diff --git a/apps/admin/src/pages/Attendance/index.tsx b/apps/admin/src/pages/Attendance/index.tsx index 7339870..be6bd79 100644 --- a/apps/admin/src/pages/Attendance/index.tsx +++ b/apps/admin/src/pages/Attendance/index.tsx @@ -1,20 +1,7 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { - Table, - Button, - Modal, - Form, - DatePicker, - Select, - Space, - message, - Tag, - Card, - Input, - Tooltip, - Row, - Col, - Tabs, + Table, Button, Modal, Form, DatePicker, Select, Space, message, + Tag, Card, Input, Tooltip, Row, Col, Tabs, Alert, } from 'antd'; import { PlusOutlined, @@ -115,6 +102,7 @@ interface DingRecord { matchStatus: string; studentId?: number; } +interface AlertItem { studentId: number; studentName: string; className: string; type: string; count: number; lastDate: string } // ── Component ── @@ -126,6 +114,7 @@ const AttendancePage: React.FC = () => { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [classOptions, setClassOptions] = useState([]); + const [alerts, setAlerts] = useState([]); // Filters const [filterClassId, setFilterClassId] = useState(undefined); @@ -192,8 +181,9 @@ const AttendancePage: React.FC = () => { message.success('更新成功'); setEditModalOpen(false); fetchRecords(); - } catch (e: any) { - message.error(e?.message || '更新失败'); + } catch (e: unknown) { + const err = e as { message?: string }; + message.error(err?.message || '更新失败'); } }; @@ -275,6 +265,7 @@ const AttendancePage: React.FC = () => { useEffect(() => { fetchClasses(); }, [fetchClasses]); + useEffect(() => { api.get('/attendance-records/alerts').then(setAlerts).catch(() => {}) }, []); useEffect(() => { if (calendarView) { @@ -577,6 +568,11 @@ const AttendancePage: React.FC = () => { // ── Render ── return (
+ {alerts.length > 0 && ( + `${a.studentName}(${a.className || '-'}):${a.type} ${a.count}次,最近${a.lastDate}`).join(';')} + style={{ marginBottom: 16 }} />)} **For agentic workers:** Use subagent-driven-development. Steps use checkbox syntax. + +**Goal:** Add missing filter dropdowns to 7 list pages, implement attendance anomaly detection, and wire up real DingTalk/WeCom sync API calls. + +**Architecture:** Frontend: add Ant Design `` dropdown (active/graduated/withdrawn/archived) +- [ ] Add `filterTenantId` state + `` (available/in_use/maintenance/archived) +- [ ] Wire to API query param + +--- + +### Task 3: Occupancies Page — Status + Date Range + +**Files:** +- Modify: `apps/admin/src/pages/Occupancies/index.tsx` + +- [ ] Add `filterStatus` (checked_in/checked_out) +- [ ] Add `` for check-in date range +- [ ] Wire to API params + +--- + +### Task 4: Remaining Pages — Status Filters + +**Files:** +- Modify: `apps/admin/src/pages/Classrooms/index.tsx` — status dropdown +- Modify: `apps/admin/src/pages/Expenses/index.tsx` — status filter +- Modify: `apps/admin/src/pages/Tenants/index.tsx` — status dropdown + +- [ ] Each page: add `` with water/electricity/cleaning/rent/other +- [ ] Wire to API + +--- + +### Task 6: Attendance Anomaly Detection + +**Files:** +- Modify: `apps/server/src/attendance/attendance.service.ts` — add `getAnomalies()` method +- Modify: `apps/server/src/attendance/attendance.controller.ts` — add `GET /attendance-records/anomalies` +- Modify: `apps/admin/src/pages/Attendance/index.tsx` — add Alert banner + +- [ ] Backend: `getAnomalies()` queries students with ≥3 consecutive absences or ≥5 lates in 7 days +- [ ] Backend: returns `{ studentId, studentName, type: 'consecutive_absence'|'frequent_late', count, dateRange }` +- [ ] Frontend: `` banner at top of page showing anomaly count +- [ ] Frontend: click to filter records for that student + +--- + +### Task 7: Sync Stubs → Real Implementation + +**Files:** +- Modify: `apps/server/src/sync/sync.service.ts` + +- [ ] Check `process.env.DINGTALK_APP_KEY` before attempting ding sync +- [ ] Check `process.env.WECOM_CORP_ID` before attempting wecom sync +- [ ] Log actionable messages: "DingTalk not configured, set DINGTALK_APP_KEY" +- [ ] If configured, attempt real API calls with proper error handling +- [ ] Record sync counts in SyncLog