feat: enhance dashboard with comprehensive stats and charts

This commit is contained in:
2026-07-05 21:52:50 +08:00
parent ab5898e2cf
commit 2be8b55c93
3 changed files with 167 additions and 26 deletions

View File

@@ -8,11 +8,15 @@ import { RoomExpense } from '../entities/room-expense.entity';
import { Classroom } from '../entities/classroom.entity';
import { ClassSchedule } from '../entities/class-schedule.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';
import { Class } from '../entities/class.entity';
import { Deposit } from '../entities/deposit.entity';
import { ClassroomRental } from '../entities/classroom-rental.entity';
import { ClassTeacher } from '../entities/class-teacher.entity';
import { DashboardService } from './dashboard.service';
import { DashboardController } from './dashboard.controller';
@Module({
imports: [TypeOrmModule.forFeature([Room, Student, Occupancy, Bill, RoomExpense, Classroom, ClassSchedule, AttendanceRecord])],
imports: [TypeOrmModule.forFeature([Room, Student, Occupancy, Bill, RoomExpense, Classroom, ClassSchedule, AttendanceRecord, Class, Deposit, ClassroomRental, ClassTeacher])],
controllers: [DashboardController],
providers: [DashboardService],
})

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, IsNull, Not } from 'typeorm';
import { Repository, IsNull, Not, MoreThanOrEqual } from 'typeorm';
import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity';
import { Occupancy } from '../entities/occupancy.entity';
@@ -9,6 +9,10 @@ import { RoomExpense } from '../entities/room-expense.entity';
import { Classroom } from '../entities/classroom.entity';
import { ClassSchedule } from '../entities/class-schedule.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';
import { Class } from '../entities/class.entity';
import { Deposit } from '../entities/deposit.entity';
import { ClassroomRental } from '../entities/classroom-rental.entity';
import { ClassTeacher } from '../entities/class-teacher.entity';
@Injectable()
export class DashboardService {
@@ -21,6 +25,10 @@ export class DashboardService {
@InjectRepository(Classroom) private classroomRepo: Repository<Classroom>,
@InjectRepository(ClassSchedule) private scheduleRepo: Repository<ClassSchedule>,
@InjectRepository(AttendanceRecord) private attendanceRepo: Repository<AttendanceRecord>,
@InjectRepository(Class) private classRepo: Repository<Class>,
@InjectRepository(Deposit) private depositRepo: Repository<Deposit>,
@InjectRepository(ClassroomRental) private rentalRepo: Repository<ClassroomRental>,
@InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
) {}
async getStats() {
@@ -89,6 +97,48 @@ export class DashboardService {
const attendanceTrend = await this.getAttendanceTrend(todayStr);
const incomeTrend = await this.getIncomeTrend(currentMonth);
// --- New stats ---
const classCount = await this.classRepo.count();
const teacherResult = await this.classTeacherRepo
.createQueryBuilder('ct')
.select('COUNT(DISTINCT ct.userId)', 'cnt')
.getRawOne();
const teacherCount = parseInt(teacherResult?.cnt || '0', 10);
const pendingResult = await this.depositRepo
.createQueryBuilder('d')
.select('SUM(d.amount)', 'total')
.where('d.status = :paid', { paid: 'paid' })
.andWhere('d.refundStatus IS NULL')
.getRawOne();
const pendingDeposits = parseFloat(pendingResult?.total || '0');
const activeRentals = await this.rentalRepo.count({ where: { endDate: MoreThanOrEqual(todayStr) } });
const occupancyByBuilding = await this.occRepo
.createQueryBuilder('o')
.leftJoin('o.room', 'r')
.select('r.building', 'building')
.addSelect('COUNT(*)', 'count')
.where('o.checkOutDate IS NULL')
.groupBy('r.building')
.getRawMany();
const attendanceByStatus = attTodayStats.reduce((acc, r) => {
acc[r.status] = parseInt(r.count, 10);
return acc;
}, {} as Record<string, number>);
const expenseByType = await this.expRepo
.createQueryBuilder('e')
.select('e.expenseType', 'type')
.addSelect('SUM(e.amount)', 'total')
.where('e.periodStart >= :start', { start: `${currentMonth}-01` })
.andWhere('e.periodEnd <= :end', { end: this.nextMonth(currentMonth) })
.groupBy('e.expenseType')
.getRawMany();
return {
totalRooms,
totalStudents,
@@ -100,6 +150,13 @@ export class DashboardService {
classroomOccupancyRate,
todayAttendanceRate,
monthlyIncome,
classCount,
teacherCount,
pendingDeposits,
activeRentals,
occupancyByBuilding,
attendanceByStatus,
expenseByType,
attendanceTrend,
incomeTrend,
};