Browse Source

fix(admin): correct schedule lifecycle statuses

彭峰 2 tháng trước cách đây
mục cha
commit
e671e8105e

+ 4 - 2
src/app/admin/pages/operations-schedule.component.html

@@ -36,9 +36,11 @@
     <div class="status-legend" aria-label="排课状态图例">
       <span class="status-upcoming"><i></i>待开始</span>
       <span class="status-live"><i></i>上课中</span>
-      <span class="status-completed"><i></i>已完成</span>
-      <span class="status-archived"><i></i>已归档</span>
+      <span class="status-coach-ended"><i></i>陪练已结束</span>
+      <span class="status-confirmed"><i></i>学员已确认</span>
+      <span class="status-completed"><i></i>已完课</span>
       <span class="status-overdue"><i></i>逾期未上课</span>
+      <span class="status-overdue-incomplete"><i></i>逾期未完课</span>
       <span class="status-cancelled"><i></i>已取消</span>
     </div>
   </header>

+ 19 - 3
src/app/admin/pages/operations-schedule.component.scss

@@ -216,13 +216,19 @@ textarea:focus-visible {
   --status-border: #bbf7d0;
 }
 
-.status-completed {
+.status-coach-ended {
   --status-color: #7c3aed;
   --status-bg: #f5f3ff;
   --status-border: #ddd6fe;
 }
 
-.status-archived {
+.status-confirmed {
+  --status-color: #0f766e;
+  --status-bg: #f0fdfa;
+  --status-border: #99f6e4;
+}
+
+.status-completed {
   --status-color: #64748b;
   --status-bg: #f8fafc;
   --status-border: #dbe3ec;
@@ -234,6 +240,12 @@ textarea:focus-visible {
   --status-border: #fecdd3;
 }
 
+.status-overdue-incomplete {
+  --status-color: #b45309;
+  --status-bg: #fffbeb;
+  --status-border: #fcd34d;
+}
+
 .status-cancelled {
   --status-color: #c2410c;
   --status-bg: #fff7ed;
@@ -248,9 +260,11 @@ textarea:focus-visible {
 
 .appointment.status-upcoming,
 .appointment.status-live,
+.appointment.status-coach-ended,
+.appointment.status-confirmed,
 .appointment.status-completed,
-.appointment.status-archived,
 .appointment.status-overdue,
+.appointment.status-overdue-incomplete,
 .appointment.status-cancelled,
 .appointment.status-neutral {
   border-color: var(--status-border);
@@ -260,6 +274,8 @@ textarea:focus-visible {
 
 .appointment.status-overdue strong,
 .appointment.status-overdue small,
+.appointment.status-overdue-incomplete strong,
+.appointment.status-overdue-incomplete small,
 .appointment.status-cancelled strong,
 .appointment.status-cancelled small {
   color: var(--status-color);

+ 14 - 4
src/app/admin/pages/operations-schedule.component.spec.ts

@@ -26,11 +26,21 @@ describe('排课状态视觉分类', () => {
     expect(scheduleStatusLabel(item, now)).toBe('逾期未上课');
   });
 
-  it('区分待开始、上课中、已完成、已归档和已取消', () => {
+  it('已经超过结束时间的上课中记录不再显示为正在上课', () => {
+    const item = { cancelled: false, statusCode: 10, statusLabel: '上课中', endsAt: '2026-08-20T01:00:00.000Z' };
+    expect(scheduleStatusTone(item, now)).toBe('status-overdue-incomplete');
+    expect(scheduleStatusLabel(item, now)).toBe('逾期未完课');
+  });
+
+  it('按原系统状态链区分陪练结束、学员确认和最终完课', () => {
     expect(scheduleStatusTone({ cancelled: false, statusCode: 0, endsAt: '2026-08-21T01:00:00.000Z' }, now)).toBe('status-upcoming');
-    expect(scheduleStatusTone({ cancelled: false, statusCode: 10, endsAt: '' }, now)).toBe('status-live');
-    expect(scheduleStatusTone({ cancelled: false, statusCode: 11, endsAt: '' }, now)).toBe('status-completed');
-    expect(scheduleStatusTone({ cancelled: false, statusCode: 30, endsAt: '' }, now)).toBe('status-archived');
+    expect(scheduleStatusTone({ cancelled: false, statusCode: 10, endsAt: '2026-08-20T03:00:00.000Z' }, now)).toBe('status-live');
+    expect(scheduleStatusTone({ cancelled: false, statusCode: 11, endsAt: '' }, now)).toBe('status-coach-ended');
+    expect(scheduleStatusLabel({ cancelled: false, statusCode: 11, statusLabel: '已完成', endsAt: '' }, now)).toBe('陪练已结束');
+    expect(scheduleStatusTone({ cancelled: false, statusCode: 20, endsAt: '' }, now)).toBe('status-confirmed');
+    expect(scheduleStatusLabel({ cancelled: false, statusCode: 20, statusLabel: '已确认', endsAt: '' }, now)).toBe('学员已确认');
+    expect(scheduleStatusTone({ cancelled: false, statusCode: 30, endsAt: '' }, now)).toBe('status-completed');
+    expect(scheduleStatusLabel({ cancelled: false, statusCode: 30, statusLabel: '已归档', endsAt: '' }, now)).toBe('已完课');
     expect(scheduleStatusTone({ cancelled: true, statusCode: 0, endsAt: '' }, now)).toBe('status-cancelled');
   });
 });

+ 11 - 4
src/app/admin/pages/operations-schedule.component.ts

@@ -8,21 +8,28 @@ import { OperationsService } from '../operations.service';
 const localDateKey=(value:Date):string=>`${value.getFullYear()}-${String(value.getMonth()+1).padStart(2,'0')}-${String(value.getDate()).padStart(2,'0')}`;
 
 type SearchField='member'|'course'|'coach';
-export type ScheduleStatusTone='status-upcoming'|'status-live'|'status-completed'|'status-archived'|'status-overdue'|'status-cancelled'|'status-neutral';
+export type ScheduleStatusTone='status-upcoming'|'status-live'|'status-coach-ended'|'status-confirmed'|'status-completed'|'status-overdue'|'status-overdue-incomplete'|'status-cancelled'|'status-neutral';
+const scheduleStatusLabels:Partial<Record<number,string>>={0:'待开始',10:'上课中',11:'陪练已结束',20:'学员已确认',30:'已完课'};
 
 export function scheduleStatusTone(item:Pick<ScheduleAppointment,'cancelled'|'statusCode'|'endsAt'>,now=Date.now()):ScheduleStatusTone{
   if(item.cancelled)return 'status-cancelled';
   const endTime=Date.parse(item.endsAt);
   if(item.statusCode===0&&Number.isFinite(endTime)&&endTime<now)return 'status-overdue';
+  if(item.statusCode===10&&Number.isFinite(endTime)&&endTime<now)return 'status-overdue-incomplete';
   if(item.statusCode===0)return 'status-upcoming';
   if(item.statusCode===10)return 'status-live';
-  if(item.statusCode===11||item.statusCode===20)return 'status-completed';
-  if(item.statusCode>=30)return 'status-archived';
+  if(item.statusCode===11)return 'status-coach-ended';
+  if(item.statusCode===20)return 'status-confirmed';
+  if(item.statusCode===30)return 'status-completed';
   return 'status-neutral';
 }
 
 export function scheduleStatusLabel(item:Pick<ScheduleAppointment,'cancelled'|'statusCode'|'statusLabel'|'endsAt'>,now=Date.now()):string{
-  return scheduleStatusTone(item,now)==='status-overdue'?'逾期未上课':item.statusLabel;
+  const tone=scheduleStatusTone(item,now);
+  if(tone==='status-cancelled')return '已取消';
+  if(tone==='status-overdue')return '逾期未上课';
+  if(tone==='status-overdue-incomplete')return '逾期未完课';
+  return scheduleStatusLabels[item.statusCode]||item.statusLabel||`状态 ${item.statusCode}`;
 }
 
 export function matchesScheduleSearch(query:string,values:Array<string|number|undefined|null>):boolean{