|
|
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
|
|
import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|
|
-import { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronRight, Clock3, LoaderCircle, LucideAngularModule, RotateCcw, Sparkles } from 'lucide-angular';
|
|
|
+import { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronLeft, ChevronRight, Clock3, LoaderCircle, LucideAngularModule, RotateCcw, Sparkles } from 'lucide-angular';
|
|
|
import { catchError, finalize, forkJoin, of } from 'rxjs';
|
|
|
import { ApiError, ApiService } from '../../core/api.service';
|
|
|
import { extractReadingWords, readNcloudSessionToken } from '../../core/external-reading';
|
|
|
@@ -11,6 +11,13 @@ import { SessionService } from '../../core/session.service';
|
|
|
|
|
|
type Item = Record<string, unknown>;
|
|
|
|
|
|
+interface DatePickerDay {
|
|
|
+ date: string;
|
|
|
+ day: number;
|
|
|
+ inMonth: boolean;
|
|
|
+ isToday: boolean;
|
|
|
+}
|
|
|
+
|
|
|
@Component({
|
|
|
selector: 'app-study-records-page',
|
|
|
standalone: true,
|
|
|
@@ -28,6 +35,23 @@ export class StudyRecordsPageComponent implements OnInit {
|
|
|
readonly error = signal('');
|
|
|
readonly totalIncome = signal(0);
|
|
|
readonly activeTab = signal(0);
|
|
|
+ readonly datePickerOpen = signal(false);
|
|
|
+ readonly datePickerMonth = signal(this.firstOfMonth(new Date()));
|
|
|
+ readonly datePickerMonthLabel = computed(() => new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long' }).format(this.datePickerMonth()));
|
|
|
+ readonly datePickerDays = computed<DatePickerDay[]>(() => {
|
|
|
+ const month = this.datePickerMonth();
|
|
|
+ const year = month.getFullYear();
|
|
|
+ const monthIndex = month.getMonth();
|
|
|
+ const offset = new Date(year, monthIndex, 1, 12).getDay();
|
|
|
+ const start = new Date(year, monthIndex, 1 - offset, 12);
|
|
|
+ const today = this.dateKey(new Date());
|
|
|
+ return Array.from({ length: 42 }, (_, index) => {
|
|
|
+ const date = new Date(start);
|
|
|
+ date.setDate(start.getDate() + index);
|
|
|
+ const key = this.dateKey(date);
|
|
|
+ return { date: key, day: date.getDate(), inMonth: date.getMonth() === monthIndex, isToday: key === today };
|
|
|
+ });
|
|
|
+ });
|
|
|
readonly visibleItems = computed(() => this.items());
|
|
|
readonly latestReadingRecord = computed(() => this.items().find((item) => extractReadingWords(item['xxqs']).length > 0));
|
|
|
readonly latestReadingWordCount = computed(() => {
|
|
|
@@ -41,7 +65,7 @@ export class StudyRecordsPageComponent implements OnInit {
|
|
|
return new Intl.DateTimeFormat('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })
|
|
|
.format(new Date(year, month - 1, day, 12));
|
|
|
};
|
|
|
- readonly icons = { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronRight, Clock3, LoaderCircle, RotateCcw, Sparkles };
|
|
|
+ readonly icons = { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronLeft, ChevronRight, Clock3, LoaderCircle, RotateCcw, Sparkles };
|
|
|
studentId = 0;
|
|
|
selectedDate = '';
|
|
|
|
|
|
@@ -89,6 +113,7 @@ export class StudyRecordsPageComponent implements OnInit {
|
|
|
onFilterChange(): void {
|
|
|
this.studentId = Number(this.studentId || 0);
|
|
|
this.selectedDate = this.dateKey(this.selectedDate);
|
|
|
+ this.syncDatePickerMonth();
|
|
|
this.syncHistoryState();
|
|
|
this.load();
|
|
|
}
|
|
|
@@ -99,6 +124,44 @@ export class StudyRecordsPageComponent implements OnInit {
|
|
|
this.load();
|
|
|
}
|
|
|
|
|
|
+ toggleDatePicker(): void {
|
|
|
+ if (!this.datePickerOpen()) this.syncDatePickerMonth();
|
|
|
+ this.datePickerOpen.update((open) => !open);
|
|
|
+ }
|
|
|
+
|
|
|
+ closeDatePicker(): void { this.datePickerOpen.set(false); }
|
|
|
+
|
|
|
+ shiftDatePickerMonth(amount: number): void {
|
|
|
+ const month = this.datePickerMonth();
|
|
|
+ this.datePickerMonth.set(new Date(month.getFullYear(), month.getMonth() + amount, 1, 12));
|
|
|
+ }
|
|
|
+
|
|
|
+ selectDate(date: string): void {
|
|
|
+ this.selectedDate = date;
|
|
|
+ this.closeDatePicker();
|
|
|
+ this.syncHistoryState();
|
|
|
+ this.load();
|
|
|
+ }
|
|
|
+
|
|
|
+ selectToday(): void {
|
|
|
+ const today = this.dateKey(new Date());
|
|
|
+ this.datePickerMonth.set(this.firstOfMonth(new Date()));
|
|
|
+ this.selectDate(today);
|
|
|
+ }
|
|
|
+
|
|
|
+ clearDate(): void {
|
|
|
+ this.selectedDate = '';
|
|
|
+ this.closeDatePicker();
|
|
|
+ this.syncHistoryState();
|
|
|
+ this.load();
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedDateLabel(): string {
|
|
|
+ if (!this.selectedDate) return '全部日期';
|
|
|
+ const [year, month, day] = this.selectedDate.split('-').map(Number);
|
|
|
+ return `${year}年${String(month).padStart(2, '0')}月${String(day).padStart(2, '0')}日`;
|
|
|
+ }
|
|
|
+
|
|
|
courseId(item: Item): string {
|
|
|
return String(item['kcid'] ?? item['kcId'] ?? item['NodeID'] ?? item['nodeId'] ?? '');
|
|
|
}
|
|
|
@@ -247,6 +310,7 @@ export class StudyRecordsPageComponent implements OnInit {
|
|
|
const query = this.route.snapshot.queryParamMap;
|
|
|
this.studentId = Number(query.get('uid') ?? 0);
|
|
|
this.selectedDate = this.dateKey(query.get('date') ?? query.get('hisDay') ?? '');
|
|
|
+ this.syncDatePickerMonth();
|
|
|
}
|
|
|
|
|
|
private syncHistoryState(): void {
|
|
|
@@ -277,4 +341,12 @@ export class StudyRecordsPageComponent implements OnInit {
|
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
|
}
|
|
|
|
|
|
+ private firstOfMonth(date: Date): Date { return new Date(date.getFullYear(), date.getMonth(), 1, 12); }
|
|
|
+
|
|
|
+ private syncDatePickerMonth(): void {
|
|
|
+ if (!this.selectedDate) return;
|
|
|
+ const [year, month] = this.selectedDate.split('-').map(Number);
|
|
|
+ this.datePickerMonth.set(new Date(year, month - 1, 1, 12));
|
|
|
+ }
|
|
|
+
|
|
|
}
|