|
|
@@ -1,8 +1,8 @@
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
-import { ChangeDetectionStrategy, Component, OnInit, computed, inject, signal } from '@angular/core';
|
|
|
+import { ChangeDetectionStrategy, Component, HostListener, OnInit, computed, inject, signal } from '@angular/core';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
|
|
-import { ArrowLeft, Building2, CalendarDays, Clock3, GraduationCap, LucideAngularModule, Phone, Plus, RefreshCw, Star, Trash2, UserRound, WalletCards } from 'lucide-angular';
|
|
|
+import { ArrowLeft, Building2, CalendarDays, ChevronDown, ChevronLeft, ChevronRight, Clock3, GraduationCap, LucideAngularModule, Phone, Plus, RefreshCw, Star, Trash2, UserRound, WalletCards } from 'lucide-angular';
|
|
|
import { CoachDetailData, ScheduleAppointment } from '../admin.models';
|
|
|
import { OperationsService } from '../operations.service';
|
|
|
import { ScheduleStatusTone, scheduleStatusLabel, scheduleStatusTone } from './operations-schedule-status';
|
|
|
@@ -12,15 +12,23 @@ const CURRENT_MONTH = new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/S
|
|
|
|
|
|
@Component({selector:'app-operations-coach-detail',standalone:true,imports:[CommonModule,FormsModule,RouterLink,LucideAngularModule],templateUrl:'./operations-coach-detail.component.html',styleUrls:['./operations-professional.scss','./operations-coach-detail.component.scss'],changeDetection:ChangeDetectionStrategy.OnPush})
|
|
|
export class OperationsCoachDetailComponent implements OnInit{
|
|
|
- readonly data=signal<CoachDetailData|null>(null);readonly loading=signal(true);readonly sectionLoading=signal<MonthSection|null>(null);readonly saving=signal(false);readonly error=signal('');readonly message=signal('');readonly tab=signal<'schedule'|'lessons'|'reviews'|'availability'|'payroll'>('schedule');readonly summaryMonth=signal(CURRENT_MONTH);readonly scheduleMonth=signal(CURRENT_MONTH);readonly lessonsMonth=signal(CURRENT_MONTH);readonly payrollMonth=signal(CURRENT_MONTH);readonly lessonsPayroll=signal<CoachDetailData['currentPayroll']|null>(null);readonly ruleType=signal<'weekly'|'date'>('weekly');readonly weekday=signal(1);readonly ruleDate=signal('');readonly startTime=signal('09:00');readonly endTime=signal('21:00');readonly available=signal(true);readonly ruleReason=signal('常规工作时间');readonly upcomingCount=computed(()=>this.data()?.appointments.filter((row)=>!row.cancelled&&row.statusCode<30&&new Date(row.endsAt).getTime()>=Date.now()).length||0);readonly icons={ArrowLeft,Building2,CalendarDays,Clock3,GraduationCap,Phone,Plus,RefreshCw,Star,Trash2,UserRound,WalletCards};private readonly operations=inject(OperationsService);private readonly objectId=inject(ActivatedRoute).snapshot.paramMap.get('objectId')||'';
|
|
|
+ readonly data=signal<CoachDetailData|null>(null);readonly loading=signal(true);readonly sectionLoading=signal<MonthSection|null>(null);readonly saving=signal(false);readonly error=signal('');readonly message=signal('');readonly tab=signal<'schedule'|'lessons'|'reviews'|'availability'|'payroll'>('schedule');readonly currentMonth=CURRENT_MONTH;readonly summaryMonth=signal(CURRENT_MONTH);readonly scheduleMonth=signal(CURRENT_MONTH);readonly lessonsMonth=signal(CURRENT_MONTH);readonly payrollMonth=signal(CURRENT_MONTH);readonly lessonsPayroll=signal<CoachDetailData['currentPayroll']|null>(null);readonly activeMonthPicker=signal<MonthSection|null>(null);readonly monthPickerYear=signal(new Date().getFullYear());readonly monthNames=['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];readonly ruleType=signal<'weekly'|'date'>('weekly');readonly weekday=signal(1);readonly ruleDate=signal('');readonly startTime=signal('09:00');readonly endTime=signal('21:00');readonly available=signal(true);readonly ruleReason=signal('常规工作时间');readonly upcomingCount=computed(()=>this.data()?.appointments.filter((row)=>!row.cancelled&&row.statusCode<30&&new Date(row.endsAt).getTime()>=Date.now()).length||0);readonly icons={ArrowLeft,Building2,CalendarDays,ChevronDown,ChevronLeft,ChevronRight,Clock3,GraduationCap,Phone,Plus,RefreshCw,Star,Trash2,UserRound,WalletCards};private readonly operations=inject(OperationsService);private readonly objectId=inject(ActivatedRoute).snapshot.paramMap.get('objectId')||'';
|
|
|
ngOnInit():void{void this.load();}
|
|
|
async load(refresh=false):Promise<void>{this.loading.set(true);this.error.set('');try{const detail=await this.operations.coachDetail(this.objectId,this.summaryMonth(),refresh),current=this.data();if(!current){this.data.set(detail);this.scheduleMonth.set(detail.payrollMonth);this.lessonsMonth.set(detail.payrollMonth);this.payrollMonth.set(detail.payrollMonth);this.lessonsPayroll.set(detail.currentPayroll);}else{this.data.set({...detail,appointments:this.scheduleMonth()===detail.payrollMonth?detail.appointments:current.appointments,lessons:this.lessonsMonth()===detail.payrollMonth?detail.lessons:current.lessons,payrolls:this.payrollMonth()===detail.payrollMonth?detail.payrolls:current.payrolls});if(this.lessonsMonth()===detail.payrollMonth)this.lessonsPayroll.set(detail.currentPayroll);}this.summaryMonth.set(detail.payrollMonth);}catch(error){this.error.set(error instanceof Error?error.message:'陪练详情加载失败');}finally{this.loading.set(false);}}
|
|
|
async changeSectionMonth(section:MonthSection,value:string):Promise<void>{if(!/^\d{4}-\d{2}$/.test(value)||value===this.sectionMonth(section))return;const previous=this.sectionMonth(section);this.setSectionMonth(section,value);this.sectionLoading.set(section);this.error.set('');try{const detail=await this.operations.coachDetail(this.objectId,value,false);this.data.update((current)=>{if(!current)return detail;if(section==='schedule')return{...current,appointments:detail.appointments};if(section==='lessons')return{...current,lessons:detail.lessons,reviews:detail.reviews};return{...current,payrolls:detail.payrolls};});if(section==='lessons')this.lessonsPayroll.set(detail.currentPayroll);}catch(error){this.setSectionMonth(section,previous);this.error.set(error instanceof Error?error.message:'月份数据加载失败');}finally{this.sectionLoading.set(null);}}
|
|
|
+ @HostListener('document:click') closeMonthPicker():void{this.activeMonthPicker.set(null);}
|
|
|
+ @HostListener('document:keydown.escape') closeMonthPickerWithKeyboard():void{this.closeMonthPicker();}
|
|
|
+ toggleMonthPicker(section:MonthSection,event:Event):void{event.stopPropagation();if(this.loading()||this.isSectionLoading(section))return;if(this.activeMonthPicker()===section){this.activeMonthPicker.set(null);return;}const year=Number(this.sectionMonth(section).slice(0,4));this.monthPickerYear.set(year||new Date().getFullYear());this.activeMonthPicker.set(section);}
|
|
|
+ shiftMonthPickerYear(delta:number,event:Event):void{event.stopPropagation();this.monthPickerYear.update((year)=>year+delta);}
|
|
|
+ async selectSectionMonth(section:MonthSection,monthIndex:number):Promise<void>{const value=this.monthOptionValue(monthIndex);this.activeMonthPicker.set(null);await this.changeSectionMonth(section,value);}
|
|
|
+ async selectCurrentMonth(section:MonthSection):Promise<void>{this.activeMonthPicker.set(null);await this.changeSectionMonth(section,this.currentMonth);}
|
|
|
+ monthLabel(value:string):string{const [year,month]=value.split('-');return year&&month?`${year}年${Number(month)}月`:'选择月份';}
|
|
|
+ monthOptionValue(monthIndex:number):string{return `${this.monthPickerYear()}-${String(monthIndex+1).padStart(2,'0')}`;}
|
|
|
isSectionLoading(section:MonthSection):boolean{return this.sectionLoading()===section;}
|
|
|
statusTone(row:ScheduleAppointment):ScheduleStatusTone{return scheduleStatusTone(row);}
|
|
|
statusDisplayLabel(row:ScheduleAppointment):string{return scheduleStatusLabel(row);}
|
|
|
async saveAvailability():Promise<void>{const coach=this.data()?.coach;if(!coach)return;this.saving.set(true);try{await this.operations.mutate('ops/coach-availability/save','',this.ruleReason(),{coachId:coach.userId,ruleType:this.ruleType(),weekday:this.weekday(),date:this.ruleDate(),startTime:this.startTime(),endTime:this.endTime(),available:this.available()});this.message.set('可用时间已保存');await this.load(true);}catch(error){this.error.set(error instanceof Error?error.message:'可用时间保存失败');}finally{this.saving.set(false);}}
|
|
|
async removeAvailability(objectId:string,updatedAt:string):Promise<void>{this.saving.set(true);try{await this.operations.mutate('ops/coach-availability/delete',objectId,'删除错误的可用时间规则',{},updatedAt);await this.load(true);}catch(error){this.error.set(error instanceof Error?error.message:'删除失败');}finally{this.saving.set(false);}}
|
|
|
- private sectionMonth(section:MonthSection):string{return section==='schedule'?this.scheduleMonth():section==='lessons'?this.lessonsMonth():this.payrollMonth();}
|
|
|
+ sectionMonth(section:MonthSection):string{return section==='schedule'?this.scheduleMonth():section==='lessons'?this.lessonsMonth():this.payrollMonth();}
|
|
|
private setSectionMonth(section:MonthSection,value:string):void{if(section==='schedule')this.scheduleMonth.set(value);else if(section==='lessons')this.lessonsMonth.set(value);else this.payrollMonth.set(value);}
|
|
|
}
|