import { DropdownDirective } from '../shared/dropdown.directive'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit, computed, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, RouterLink } from '@angular/router'; 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 { OperationsLiveRefreshService } from '../operations-live-refresh.service'; import { ScheduleStatusTone, scheduleStatusLabel, scheduleStatusTone } from './operations-schedule-status'; type MonthSection = 'schedule' | 'lessons' | 'payroll'; const currentMonth = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' }).slice(0, 7); @Component({selector:'app-operations-coach-detail',standalone:true,imports: [DropdownDirective, 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,OnDestroy{ readonly data=signal(null);readonly loading=signal(true);readonly sectionLoading=signal(null);readonly saving=signal(false);readonly error=signal('');readonly message=signal('');readonly tab=signal<'schedule'|'lessons'|'reviews'|'availability'|'payroll'>('schedule');readonly currentMonth=currentMonth();readonly summaryMonth=signal(currentMonth());readonly scheduleMonth=signal(currentMonth());readonly lessonsMonth=signal(currentMonth());readonly payrollMonth=signal(currentMonth());readonly lessonsPayroll=signal(null);readonly activeMonthPicker=signal(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 liveRefresh=inject(OperationsLiveRefreshService);private readonly objectId=inject(ActivatedRoute).snapshot.paramMap.get('objectId')||'';private stopLiveRefresh?:()=>void;private loadRequest=0; ngOnInit():void{void this.load();this.stopLiveRefresh=this.liveRefresh.watch(['appointments','review','account'],()=>{if(this.loading()||this.sectionLoading()||this.saving())return false;void this.load(false,true);return true;});} ngOnDestroy():void{this.stopLiveRefresh?.();} async load(refresh=false,background=false):Promise{const request=++this.loadRequest;if(!background)this.loading.set(true);this.error.set('');try{const detail=await this.operations.coachDetail(this.objectId,this.summaryMonth(),refresh);if(request!==this.loadRequest)return;const 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){if(request===this.loadRequest)this.error.set(error instanceof Error?error.message:'陪练详情加载失败');}finally{if(!background&&request===this.loadRequest)this.loading.set(false);}} async changeSectionMonth(section:MonthSection,value:string):Promise{if(!/^\d{4}-\d{2}$/.test(value)||value===this.sectionMonth(section))return;this.loadRequest++;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{const value=this.monthOptionValue(monthIndex);this.activeMonthPicker.set(null);await this.changeSectionMonth(section,value);} async selectCurrentMonth(section:MonthSection):Promise{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{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{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);}} 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);} }