forked from wangziqi/gongxue-base
feat: add student followup report
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
bulkUpsertTenantStudents,
|
||||
loadTenantClasses,
|
||||
loadTenantStudentFollowups,
|
||||
loadTenantStudentFollowupReport,
|
||||
loadTenantStudentNotes,
|
||||
loadTenantStudents,
|
||||
loadTenantTeachers,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
type TenantStudentItem,
|
||||
type TenantStudentNoteItem,
|
||||
type TenantStudentCrmPushInput,
|
||||
type TenantStudentFollowupReport,
|
||||
type TenantTeacherItem,
|
||||
type TenantMemberItem,
|
||||
} from '@/services/tenantAdmin';
|
||||
@@ -122,6 +124,7 @@ export default function TenantStudentsPage() {
|
||||
const [bulkResult, setBulkResult] = useState<BulkOperationResult | null>(null);
|
||||
const [assignResult, setAssignResult] = useState<BulkOperationResult | null>(null);
|
||||
const [crmPushResult, setCrmPushResult] = useState<BulkOperationResult | null>(null);
|
||||
const [followupReport, setFollowupReport] = useState<TenantStudentFollowupReport['item'] | null>(null);
|
||||
const [scoped, setScoped] = useState(false);
|
||||
const [busy, setBusy] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
@@ -144,13 +147,15 @@ export default function TenantStudentsPage() {
|
||||
limit: 80,
|
||||
}),
|
||||
loadTenantStudentFollowups({ status: 'open', limit: 30 }).catch(() => ({ items: [] })),
|
||||
]).then(([classPayload, teacherPayload, memberPayload, studentPayload, followupPayload]) => {
|
||||
loadTenantStudentFollowupReport({ timeRange: '30d', classId: nextClassId || undefined, limit: 8 }).catch(() => ({ item: null })),
|
||||
]).then(([classPayload, teacherPayload, memberPayload, studentPayload, followupPayload, reportPayload]) => {
|
||||
setClasses(classPayload.items || []);
|
||||
setTeachers(teacherPayload.items || []);
|
||||
setCrmAssignees((memberPayload.items || []).filter(item => crmAssignableRoles.includes(String(item.role || ''))));
|
||||
setStudents(studentPayload.items || []);
|
||||
setScoped(studentPayload.scoped === true);
|
||||
setFollowups(followupPayload.items || []);
|
||||
setFollowupReport(reportPayload.item || null);
|
||||
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '学生数据加载失败'));
|
||||
}
|
||||
|
||||
@@ -538,6 +543,38 @@ export default function TenantStudentsPage() {
|
||||
{!students.length ? <View className='admin-empty'>暂无学生,或当前角色没有可见学生范围。</View> : null}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>跟进看板</Text>
|
||||
<View className='admin-grid'>
|
||||
<View className='admin-metric'>
|
||||
<Text className='admin-metric-label'>30 天跟进</Text>
|
||||
<Text className='admin-metric-value'>{followupReport?.summary?.total || 0}</Text>
|
||||
<Text className='admin-row-meta'>完成 {followupReport?.summary?.done || 0} · CRM {followupReport?.summary?.crmPushTasks || 0}</Text>
|
||||
</View>
|
||||
<View className='admin-metric'>
|
||||
<Text className='admin-metric-label'>待处理</Text>
|
||||
<Text className='admin-metric-value'>{followupReport?.summary?.openBacklog || 0}</Text>
|
||||
<Text className='admin-row-meta'>逾期 {followupReport?.summary?.overdueBacklog || 0} · 高优先级 {followupReport?.summary?.highPriority || 0}</Text>
|
||||
</View>
|
||||
<View className='admin-metric'>
|
||||
<Text className='admin-metric-label'>完成率</Text>
|
||||
<Text className='admin-metric-value'>{Math.round((followupReport?.summary?.completionRate || 0) * 100)}%</Text>
|
||||
<Text className='admin-row-meta'>平均 {followupReport?.summary?.avgCompleteHours || 0} 小时</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='admin-list'>
|
||||
{(followupReport?.byAssignee || []).slice(0, 4).map((item, index) => (
|
||||
<View className='admin-row' key={`${item.assignedToUserId || 'none'}:${index}`}>
|
||||
<Text className='admin-row-main'>{String(item.assignedToName || '未指派')} · {String(item.assignedToRole || '-')}</Text>
|
||||
<Text className='admin-row-meta'>总数 {String(item.total || 0)} · 待处理 {String(item.open || 0)} · 完成 {String(item.done || 0)} · 逾期 {String(item.overdue || 0)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{(followupReport?.overdueItems || []).slice(0, 3).map(item => (
|
||||
<Text className='admin-row-meta break-line' key={item.id}>逾期 · {item.title || '跟进任务'} · {item.studentName || item.studentUserId || '-'} · {item.assignedToName || '未指派'}</Text>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>CRM 批量推送</Text>
|
||||
<View className='admin-form-grid'>
|
||||
|
||||
Reference in New Issue
Block a user