fix(admin,server): 修复 Dashboard 甘特图时间线与数据校验
Some checks failed
CI / check (pull_request) Failing after 1m54s

This commit is contained in:
2026-08-07 16:38:16 +08:00
parent 5f9566e26d
commit 8d4ebcf9c0
9 changed files with 160 additions and 19 deletions

View File

@@ -55,7 +55,7 @@ export interface ExpenseByTypeRow {
}
export interface GanttOccupancy {
studentName: string;
studentId?: string;
studentId?: string | number;
checkInDate: string;
checkOutDate: string | null;
billingStartDate?: string;

View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vitest';
import { buildGanttOption } from './DashboardCharts';
import type { GanttOccupancy } from './Dashboard.types';
const ganttRoom = (occupancies: GanttOccupancy[]) => [
{ roomNumber: 'A101', occupancies },
];
describe('buildGanttOption 甘特图时间线', () => {
it('未退宿的入住条在查看过去月份时截断到 periodEnd而不是画到今天', () => {
const option = buildGanttOption(
ganttRoom([
{
studentName: '张三',
checkInDate: '2026-05-01',
checkOutDate: null,
},
]),
{ periodEnd: '2026-06-30', today: '2026-08-07' },
);
const series = option.series as Array<{ data: Array<{ value: [string, string, string, boolean] }> }>;
expect(series[0].data[0].value[2]).toBe('2026-06-30');
expect(series[0].data[0].value[3]).toBe(true);
});
it('未退宿的入住条在查看当前月时截断到今天', () => {
const option = buildGanttOption(
ganttRoom([
{
studentName: '张三',
checkInDate: '2026-07-01',
checkOutDate: null,
},
]),
{ periodEnd: '2026-08-31', today: '2026-08-07' },
);
const series = option.series as Array<{ data: Array<{ value: [string, string, string, boolean] }> }>;
expect(series[0].data[0].value[2]).toBe('2026-08-07');
});
it('已退宿的入住条保留真实退宿日期', () => {
const option = buildGanttOption(
ganttRoom([
{
studentName: '李四',
checkInDate: '2026-05-01',
checkOutDate: '2026-06-15',
},
]),
{ periodEnd: '2026-06-30', today: '2026-08-07' },
);
const series = option.series as Array<{ data: Array<{ value: [string, string, string, boolean] }> }>;
expect(series[0].data[0].value[2]).toBe('2026-06-15');
expect(series[0].data[0].value[3]).toBe(false);
});
});

View File

@@ -1,4 +1,5 @@
import type { EChartsOption } from '../../components/ECharts';
import dayjs from 'dayjs';
import {
attendanceLabelMap,
COLORS,
@@ -201,7 +202,13 @@ export function buildClassroomHeatmapOption(
};
}
export function buildGanttOption(ganttData: GanttRoom[]): EChartsOption {
export function buildGanttOption(
ganttData: GanttRoom[],
options?: { periodEnd?: string; today?: string },
): EChartsOption {
const today = options?.today ?? dayjs().format('YYYY-MM-DD');
const periodEnd = options?.periodEnd;
return {
tooltip: {
formatter: (p: { data: { name: string; value: [string, string, string, boolean] } }) =>
@@ -246,15 +253,18 @@ export function buildGanttOption(ganttData: GanttRoom[]): EChartsOption {
},
encode: { x: [1, 2], y: 0 },
data: ganttData.flatMap((r) =>
(r.occupancies || []).map((o) => ({
name: o.studentName,
value: [
r.roomNumber,
o.checkInDate,
o.checkOutDate || new Date().toISOString().slice(0, 10),
!o.checkOutDate,
] as [string, string, string, boolean],
})),
(r.occupancies || []).map((o) => {
const activeEnd = periodEnd && periodEnd < today ? periodEnd : today;
return {
name: o.studentName,
value: [
r.roomNumber,
o.checkInDate,
o.checkOutDate || activeEnd,
!o.checkOutDate,
] as [string, string, string, boolean],
};
}),
),
},
],

View File

@@ -61,10 +61,11 @@ export const ClassroomHeatmapCard: React.FC<{
);
};
export const GanttCard: React.FC<{ data: GanttRoom[]; isMobile: boolean }> = ({
data,
isMobile,
}) => {
export const GanttCard: React.FC<{
data: GanttRoom[];
isMobile: boolean;
periodEnd?: string;
}> = ({ data, isMobile, periodEnd }) => {
const vp = useInViewport('200px');
return (
<LazySection
@@ -74,7 +75,7 @@ export const GanttCard: React.FC<{ data: GanttRoom[]; isMobile: boolean }> = ({
>
{data.length > 0 ? (
<ReactECharts
option={buildGanttOption(data)}
option={buildGanttOption(data, { periodEnd })}
style={{ width: '100%', height: isMobile ? 300 : 450 }}
/>
) : (

View File

@@ -495,7 +495,7 @@ const DashboardPage: React.FC = () => {
<ClassroomHeatmapCard data={classroomOccupancy} isMobile={isMobile} />
{/* ═══════════ 图表:入住时间线甘特图(懒加载) ═══════════ */}
<GanttCard data={ganttData} isMobile={isMobile} />
<GanttCard data={ganttData} isMobile={isMobile} periodEnd={period[1]} />
</div>
);
};