|
@@ -1,3 +1,5 @@
|
|
|
|
|
+import { LessonFeedbackComponent } from './lesson-feedback.component';
|
|
|
|
|
+import { lessonDateLabel } from './lesson-display';
|
|
|
import { DropdownDirective } from '../../shared/dropdown.directive';
|
|
import { DropdownDirective } from '../../shared/dropdown.directive';
|
|
|
import { CommonModule } from '@angular/common';
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { ChangeDetectionStrategy, Component, computed, effect, ElementRef, inject, OnInit, OnDestroy, signal, viewChild } from '@angular/core';
|
|
import { ChangeDetectionStrategy, Component, computed, effect, ElementRef, inject, OnInit, OnDestroy, signal, viewChild } from '@angular/core';
|
|
@@ -17,7 +19,7 @@ type SourceItem = Record<string, unknown>;
|
|
|
@Component({
|
|
@Component({
|
|
|
selector: 'app-appointment-page',
|
|
selector: 'app-appointment-page',
|
|
|
standalone: true,
|
|
standalone: true,
|
|
|
- imports: [DropdownDirective, CommonModule, FormsModule, LucideAngularModule],
|
|
|
|
|
|
|
+ imports: [LessonFeedbackComponent, DropdownDirective, CommonModule, FormsModule, LucideAngularModule],
|
|
|
templateUrl: './appointment-page.component.html',
|
|
templateUrl: './appointment-page.component.html',
|
|
|
styleUrl: './appointment-page.component.scss',
|
|
styleUrl: './appointment-page.component.scss',
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -42,6 +44,11 @@ export class AppointmentPageComponent implements OnInit, OnDestroy {
|
|
|
readonly error = signal('');
|
|
readonly error = signal('');
|
|
|
readonly detailWarning = signal('');
|
|
readonly detailWarning = signal('');
|
|
|
readonly feedbackError = signal('');
|
|
readonly feedbackError = signal('');
|
|
|
|
|
+ readonly editingFeedback = signal(false);
|
|
|
|
|
+ readonly feedbackSaved = signal(false);
|
|
|
|
|
+ private feedbackRevision = '';
|
|
|
|
|
+ private autoEditOpened = false;
|
|
|
|
|
+ readonly dateLabel = lessonDateLabel;
|
|
|
readonly actionError = signal('');
|
|
readonly actionError = signal('');
|
|
|
readonly detailVerified = signal(false);
|
|
readonly detailVerified = signal(false);
|
|
|
private loadRequest?: Subscription;
|
|
private loadRequest?: Subscription;
|
|
@@ -75,13 +82,14 @@ export class AppointmentPageComponent implements OnInit, OnDestroy {
|
|
|
constructor() {
|
|
constructor() {
|
|
|
effect(() => {
|
|
effect(() => {
|
|
|
const dialog = this.completionDialog()?.nativeElement;
|
|
const dialog = this.completionDialog()?.nativeElement;
|
|
|
|
|
+ if (dialog && !this.autoEditOpened && this.can('editFeedback') && this.feedback() && this.route.snapshot.queryParamMap.get('editReport') === '1') { this.autoEditOpened = true; this.openFeedbackEditor(); }
|
|
|
if (dialog && !this.isStudentView && !this.order()['cancelled'] && this.can('complete') && this.route.snapshot.queryParamMap.get('feedback') === '1') dialog.showModal();
|
|
if (dialog && !this.isStudentView && !this.order()['cancelled'] && this.can('complete') && this.route.snapshot.queryParamMap.get('feedback') === '1') dialog.showModal();
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
|
ngOnInit(): void {
|
|
|
this.load();
|
|
this.load();
|
|
|
- this.stopLive = this.liveData.watch({scopes:['appointments'],studentId:()=>undefined,appointmentPolling:true,onRefresh:()=>{if(!this.submitting())this.load(true);}});
|
|
|
|
|
|
|
+ this.stopLive = this.liveData.watch({scopes:['appointments'],studentId:()=>undefined,appointmentPolling:true,onRefresh:()=>{if(!this.submitting() && !this.completionDialog()?.nativeElement.open)this.load(true);}});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ngOnDestroy(): void { this.stopLive?.(); this.loadRequest?.unsubscribe(); this.courseRequest?.unsubscribe(); this.unitRequest?.unsubscribe(); }
|
|
ngOnDestroy(): void { this.stopLive?.(); this.loadRequest?.unsubscribe(); this.courseRequest?.unsubscribe(); this.unitRequest?.unsubscribe(); }
|
|
@@ -158,13 +166,14 @@ export class AppointmentPageComponent implements OnInit, OnDestroy {
|
|
|
delivery: ['jffs'],
|
|
delivery: ['jffs'],
|
|
|
coach: ['plxm', 'plName'],
|
|
coach: ['plxm', 'plName'],
|
|
|
};
|
|
};
|
|
|
|
|
+ if (label === 'type') return String(this.order()['courseCategoryName'] || ({1:'30分钟课',2:'60分钟课',3:'体验课',4:'课型4'} as Record<string,string>)[String(this.order()['dslx'])] || this.order()['dslx_t'] || '课程');
|
|
|
for (const key of aliases[label] ?? []) if (this.order()[key] !== undefined) return String(this.order()[key]);
|
|
for (const key of aliases[label] ?? []) if (this.order()[key] !== undefined) return String(this.order()[key]);
|
|
|
return '';
|
|
return '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
statusText(): string {
|
|
statusText(): string {
|
|
|
if(this.order()['cancelled'])return '已取消';
|
|
if(this.order()['cancelled'])return '已取消';
|
|
|
- return String(this.order()['status_txt'] ?? ({ 0: '待开始', 10: '学习中', 11: '等待学员确认', 20: '学员已确认', 30: '已完成' } as Record<number, string>)[this.status()] ?? `状态 ${this.status()}`);
|
|
|
|
|
|
|
+ return String(this.order()['status_txt'] ?? ({ 0: '待开始', 10: '学习中', 11: '等待学员确认', 20: '学员已确认', 30: '已完成', 40: '已完成' } as Record<number, string>)[this.status()] ?? `状态 ${this.status()}`);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private applyOrder(order: SourceItem): void {
|
|
private applyOrder(order: SourceItem): void {
|
|
@@ -268,25 +277,46 @@ export class AppointmentPageComponent implements OnInit, OnDestroy {
|
|
|
openCompletionFeedback(): void {
|
|
openCompletionFeedback(): void {
|
|
|
if(this.order()['cancelled']===true)return;
|
|
if(this.order()['cancelled']===true)return;
|
|
|
if (this.isStudentView || !this.can('complete') || this.submitting()) return;
|
|
if (this.isStudentView || !this.can('complete') || this.submitting()) return;
|
|
|
|
|
+ this.editingFeedback.set(false);
|
|
|
this.feedbackError.set('');
|
|
this.feedbackError.set('');
|
|
|
this.completionDialog()?.nativeElement.showModal();
|
|
this.completionDialog()?.nativeElement.showModal();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ openFeedbackEditor(): void {
|
|
|
|
|
+ if (this.isStudentView || !this.can('editFeedback') || !this.feedback() || this.submitting()) return;
|
|
|
|
|
+ const feedback = this.feedback()!;
|
|
|
|
|
+ this.contentSummary = String(feedback['contentSummary'] || '');
|
|
|
|
|
+ this.reviewStatus = String(feedback['reviewStatus'] || '');
|
|
|
|
|
+ this.reviewSummary = String(feedback['reviewSummary'] || '');
|
|
|
|
|
+ this.trainingStatus = String(feedback['trainingStatus'] || '');
|
|
|
|
|
+ this.trainingSummary = String(feedback['trainingSummary'] || '');
|
|
|
|
|
+ this.homework = String(feedback['homework'] || '');
|
|
|
|
|
+ this.noHomework = feedback['noHomework'] === true;
|
|
|
|
|
+ this.coachComment = String(feedback['coachComment'] ?? this.practiceRecord()['pldp'] ?? '');
|
|
|
|
|
+ this.notes = String(feedback['notes'] || '');
|
|
|
|
|
+ this.feedbackRevision = String(feedback['revision'] || feedback['updatedAt'] || '');
|
|
|
|
|
+ this.editingFeedback.set(true);
|
|
|
|
|
+ this.feedbackError.set('');
|
|
|
|
|
+ this.feedbackSaved.set(false);
|
|
|
|
|
+ this.completionDialog()?.nativeElement.showModal();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
closeCompletionFeedback(event?: Event): void {
|
|
closeCompletionFeedback(event?: Event): void {
|
|
|
event?.preventDefault();
|
|
event?.preventDefault();
|
|
|
if (!this.submitting()) this.completionDialog()?.nativeElement.close();
|
|
if (!this.submitting()) this.completionDialog()?.nativeElement.close();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
finishCoachLesson(): void {
|
|
finishCoachLesson(): void {
|
|
|
- if (this.submitting() || !this.can('complete') || !this.completionDialog()?.nativeElement.open) return;
|
|
|
|
|
|
|
+ if (this.submitting() || !this.can(this.editingFeedback() ? 'editFeedback' : 'complete') || !this.completionDialog()?.nativeElement.open) return;
|
|
|
this.feedbackError.set('');
|
|
this.feedbackError.set('');
|
|
|
if (!this.contentSummary.trim()) { this.feedbackError.set('请填写本次学习内容'); return; }
|
|
if (!this.contentSummary.trim()) { this.feedbackError.set('请填写本次学习内容'); return; }
|
|
|
if (!this.trainingSummary.trim()) { this.feedbackError.set('请填写进度说明'); return; }
|
|
if (!this.trainingSummary.trim()) { this.feedbackError.set('请填写进度说明'); return; }
|
|
|
if (!this.noHomework && !this.homework.trim()) { this.feedbackError.set('请填写作业安排,或选择“无作业”'); return; }
|
|
if (!this.noHomework && !this.homework.trim()) { this.feedbackError.set('请填写作业安排,或选择“无作业”'); return; }
|
|
|
this.submitting.set(true);
|
|
this.submitting.set(true);
|
|
|
- this.api.post('app_lesson_complete', {
|
|
|
|
|
|
|
+ const editing = this.editingFeedback();
|
|
|
|
|
+ this.api.post(editing ? 'app_lesson_feedback_update' : 'app_lesson_complete', {
|
|
|
orderId: this.orderId,
|
|
orderId: this.orderId,
|
|
|
- expectedUpdatedAt: this.order()['updatedAt'],
|
|
|
|
|
|
|
+ expectedUpdatedAt: editing ? this.feedbackRevision : this.order()['updatedAt'],
|
|
|
feedback: JSON.stringify({
|
|
feedback: JSON.stringify({
|
|
|
contentSummary: this.contentSummary.trim(),
|
|
contentSummary: this.contentSummary.trim(),
|
|
|
reviewStatus: this.reviewStatus.trim(),
|
|
reviewStatus: this.reviewStatus.trim(),
|
|
@@ -299,7 +329,7 @@ export class AppointmentPageComponent implements OnInit, OnDestroy {
|
|
|
coachComment: this.coachComment.trim(),
|
|
coachComment: this.coachComment.trim(),
|
|
|
}),
|
|
}),
|
|
|
}).pipe(finalize(() => this.submitting.set(false))).subscribe({
|
|
}).pipe(finalize(() => this.submitting.set(false))).subscribe({
|
|
|
- next: () => { this.completionDialog()?.nativeElement.close(); this.platform.toast('课程已结课,学习报告已生成'); this.load(); },
|
|
|
|
|
|
|
+ next: () => { this.completionDialog()?.nativeElement.close(); this.feedbackSaved.set(editing); this.platform.toast(editing ? '报告修改已保存' : '课程已结课,学习报告已生成'); this.load(); },
|
|
|
error: (error: ApiError) => this.feedbackError.set(error.message),
|
|
error: (error: ApiError) => this.feedbackError.set(error.message),
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|