mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 01:47:41 +00:00
modal consolidation
This commit is contained in:
@@ -1,56 +0,0 @@
|
|||||||
import { Component, inject, input, output, signal } from '@angular/core';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { ModalShell } from '../modal-shell/modal-shell';
|
|
||||||
import { CreateGoalDto } from '../../../shared/classes/create-goal.dto';
|
|
||||||
import { StudentGoalItem } from '../../../shared/classes/student-goal';
|
|
||||||
import { StudentService } from '../../../shared/services/student.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-add-goal-modal',
|
|
||||||
imports: [FormsModule, ModalShell],
|
|
||||||
templateUrl: './add-goal-modal.html',
|
|
||||||
styleUrl: './add-goal-modal.scss',
|
|
||||||
})
|
|
||||||
export class AddGoalModal {
|
|
||||||
private readonly studentService = inject(StudentService);
|
|
||||||
|
|
||||||
readonly studentId = input.required<string>();
|
|
||||||
readonly existingGoals = input.required<StudentGoalItem[]>();
|
|
||||||
readonly nextIepDate = input<string | null>();
|
|
||||||
readonly goalCreated = output<StudentGoalItem>();
|
|
||||||
readonly cancelled = output<void>();
|
|
||||||
|
|
||||||
protected readonly isSubmitting = signal(false);
|
|
||||||
protected readonly errorMessage = signal<string | null>(null);
|
|
||||||
|
|
||||||
protected form: CreateGoalDto = {
|
|
||||||
description: '',
|
|
||||||
category: '',
|
|
||||||
baseline: '',
|
|
||||||
goalParentId: null,
|
|
||||||
targetCompletionDate: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
const iepDate = this.nextIepDate?.();
|
|
||||||
if (iepDate) {
|
|
||||||
this.form.targetCompletionDate = iepDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onSubmit() {
|
|
||||||
if (!this.form.category.trim()) return;
|
|
||||||
this.errorMessage.set(null);
|
|
||||||
this.isSubmitting.set(true);
|
|
||||||
|
|
||||||
const result = await this.studentService.createGoal(this.studentId(), this.form);
|
|
||||||
this.isSubmitting.set(false);
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
this.errorMessage.set(result.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.goalCreated.emit(result.payload!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
<app-modal-shell title="Edit Goal" (closed)="closed.emit()">
|
|
||||||
<div class="field">
|
|
||||||
<label class="field-label">Category</label>
|
|
||||||
<input class="field-input" type="text" [(ngModel)]="category" placeholder="e.g. Reading, Math, Behavior…" />
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="field-label">Description</label>
|
|
||||||
<textarea class="field-input field-textarea" [(ngModel)]="description"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="field-label">Baseline</label>
|
|
||||||
<textarea class="field-input field-textarea" [(ngModel)]="baseline" placeholder="Enter baseline..."></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="field-label">Due Date</label>
|
|
||||||
<input class="field-input" type="date" [(ngModel)]="targetCompletionDate" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (errorMessage()) {
|
|
||||||
<p class="error">{{ errorMessage() }}</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button class="btn-secondary" (click)="closed.emit()">Cancel</button>
|
|
||||||
<button class="btn-primary" (click)="onSave()" [disabled]="saving()">
|
|
||||||
{{ saving() ? 'Saving...' : 'Save' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</app-modal-shell>
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
/* Inherits all styles from modal-shell via ::ng-deep */
|
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
import { Component, inject, input, output, signal } from '@angular/core';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { ModalShell } from '../modal-shell/modal-shell';
|
|
||||||
import { StudentService } from '../../../shared/services/student.service';
|
|
||||||
import { StudentGoalItem } from '../../../shared/classes/student-goal';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-edit-goal-modal',
|
|
||||||
imports: [FormsModule, ModalShell],
|
|
||||||
templateUrl: './edit-goal-modal.html',
|
|
||||||
styleUrl: './edit-goal-modal.scss',
|
|
||||||
})
|
|
||||||
export class EditGoalModal {
|
|
||||||
private readonly studentService = inject(StudentService);
|
|
||||||
|
|
||||||
readonly studentId = input.required<string>();
|
|
||||||
readonly goal = input.required<StudentGoalItem>();
|
|
||||||
readonly saved = output<void>();
|
|
||||||
readonly closed = output<void>();
|
|
||||||
|
|
||||||
protected readonly saving = signal(false);
|
|
||||||
protected readonly errorMessage = signal<string | null>(null);
|
|
||||||
|
|
||||||
protected category = '';
|
|
||||||
protected description = '';
|
|
||||||
protected baseline = '';
|
|
||||||
protected targetCompletionDate: string | null = null;
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
const g = this.goal();
|
|
||||||
this.category = g.category;
|
|
||||||
this.description = g.description;
|
|
||||||
this.baseline = g.baseline;
|
|
||||||
this.targetCompletionDate = g.targetCompletionDate ? g.targetCompletionDate.substring(0, 10) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onSave() {
|
|
||||||
if (!this.category.trim() || !this.description.trim()) return;
|
|
||||||
this.saving.set(true);
|
|
||||||
this.errorMessage.set(null);
|
|
||||||
|
|
||||||
const result = await this.studentService.updateGoal(this.studentId(), this.goal().goalId, {
|
|
||||||
category: this.category,
|
|
||||||
description: this.description,
|
|
||||||
baseline: this.baseline,
|
|
||||||
targetCompletionDate: this.targetCompletionDate,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.saving.set(false);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
this.studentService.notifyDataChanged();
|
|
||||||
this.saved.emit();
|
|
||||||
} else {
|
|
||||||
this.errorMessage.set(result.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
<app-modal-shell title="Add Goal" (closed)="cancelled.emit()">
|
<app-modal-shell [title]="modalTitle" (closed)="closed.emit()">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label">Category</label>
|
<label class="field-label">Category</label>
|
||||||
<input class="field-input" type="text" [(ngModel)]="form.category"
|
<input class="field-input" type="text" [(ngModel)]="form.category"
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn-secondary" (click)="cancelled.emit()">Cancel</button>
|
<button class="btn-secondary" (click)="closed.emit()">Cancel</button>
|
||||||
<button class="btn-primary" (click)="onSubmit()" [disabled]="isSubmitting() || !form.category.trim()">
|
<button class="btn-primary" (click)="onSubmit()" [disabled]="isSubmitting() || !form.category.trim()">
|
||||||
{{ isSubmitting() ? 'Saving...' : 'Add Goal' }}
|
{{ isSubmitting() ? 'Saving...' : submitLabel }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</app-modal-shell>
|
</app-modal-shell>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { Component, inject, input, output, signal } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ModalShell } from '../modal-shell/modal-shell';
|
||||||
|
import { CreateGoalDto } from '../../../shared/classes/create-goal.dto';
|
||||||
|
import { StudentGoalItem } from '../../../shared/classes/student-goal';
|
||||||
|
import { StudentService } from '../../../shared/services/student.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-goal-modal',
|
||||||
|
imports: [FormsModule, ModalShell],
|
||||||
|
templateUrl: './goal-modal.html',
|
||||||
|
styleUrl: './goal-modal.scss',
|
||||||
|
})
|
||||||
|
export class GoalModal {
|
||||||
|
private readonly studentService = inject(StudentService);
|
||||||
|
|
||||||
|
/** Required: the student this goal belongs to. */
|
||||||
|
readonly studentId = input.required<string>();
|
||||||
|
|
||||||
|
/** Optional: when provided the modal operates in edit mode. */
|
||||||
|
readonly goal = input<StudentGoalItem | null>(null);
|
||||||
|
|
||||||
|
/** Optional: used to pre-fill the target completion date in add mode. */
|
||||||
|
readonly nextIepDate = input<string | null>(null);
|
||||||
|
|
||||||
|
/** Emits the newly created goal (add mode). */
|
||||||
|
readonly goalCreated = output<StudentGoalItem>();
|
||||||
|
|
||||||
|
/** Emits when an existing goal has been saved (edit mode). */
|
||||||
|
readonly saved = output<void>();
|
||||||
|
|
||||||
|
/** Emits when the modal is dismissed without saving. */
|
||||||
|
readonly closed = output<void>();
|
||||||
|
|
||||||
|
protected readonly isSubmitting = signal(false);
|
||||||
|
protected readonly errorMessage = signal<string | null>(null);
|
||||||
|
|
||||||
|
protected form: CreateGoalDto = {
|
||||||
|
description: '',
|
||||||
|
category: '',
|
||||||
|
baseline: '',
|
||||||
|
goalParentId: null,
|
||||||
|
targetCompletionDate: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected get isEditMode(): boolean {
|
||||||
|
return !!this.goal();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get modalTitle(): string {
|
||||||
|
return this.isEditMode ? 'Edit Goal' : 'Add Goal';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get submitLabel(): string {
|
||||||
|
return this.isEditMode ? 'Save' : 'Add Goal';
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const existing = this.goal();
|
||||||
|
if (existing) {
|
||||||
|
// Edit mode — populate form from the existing goal
|
||||||
|
this.form.category = existing.category;
|
||||||
|
this.form.description = existing.description;
|
||||||
|
this.form.baseline = existing.baseline;
|
||||||
|
this.form.goalParentId = existing.goalParentId;
|
||||||
|
this.form.targetCompletionDate = existing.targetCompletionDate
|
||||||
|
? existing.targetCompletionDate.substring(0, 10)
|
||||||
|
: null;
|
||||||
|
} else {
|
||||||
|
// Add mode — pre-fill target date from IEP if available
|
||||||
|
const iepDate = this.nextIepDate?.();
|
||||||
|
if (iepDate) {
|
||||||
|
this.form.targetCompletionDate = iepDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSubmit() {
|
||||||
|
if (!this.form.category.trim()) return;
|
||||||
|
this.errorMessage.set(null);
|
||||||
|
this.isSubmitting.set(true);
|
||||||
|
|
||||||
|
if (this.isEditMode) {
|
||||||
|
const result = await this.studentService.updateGoal(
|
||||||
|
this.studentId(),
|
||||||
|
this.goal()!.goalId,
|
||||||
|
{
|
||||||
|
category: this.form.category,
|
||||||
|
description: this.form.description,
|
||||||
|
baseline: this.form.baseline,
|
||||||
|
targetCompletionDate: this.form.targetCompletionDate,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.isSubmitting.set(false);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.studentService.notifyDataChanged();
|
||||||
|
this.saved.emit();
|
||||||
|
} else {
|
||||||
|
this.errorMessage.set(result.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result = await this.studentService.createGoal(this.studentId(), this.form);
|
||||||
|
this.isSubmitting.set(false);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.goalCreated.emit(result.payload!);
|
||||||
|
} else {
|
||||||
|
this.errorMessage.set(result.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,12 @@
|
|||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<!-- Modals -->
|
<!-- Modals -->
|
||||||
@if (showAddGoalModal()) {
|
@if (showGoalModal()) {
|
||||||
<app-add-goal-modal [studentId]="studentId()!" [existingGoals]="goals()" [nextIepDate]="nextIepDate()"
|
<app-goal-modal [studentId]="studentId()!"
|
||||||
(goalCreated)="onGoalCreated($event)" (cancelled)="showAddGoalModal.set(false)" />
|
[goal]="showGoalModal() === 'add' ? null : $any(showGoalModal())"
|
||||||
}
|
[nextIepDate]="nextIepDate()"
|
||||||
@if (showEditGoalModal() && selectedGoal()) {
|
(goalCreated)="onGoalCreated($event)" (saved)="onGoalSaved()"
|
||||||
<app-edit-goal-modal [studentId]="studentId()!" [goal]="selectedGoal()!" (saved)="onEditGoalSaved()"
|
(closed)="showGoalModal.set(null)" />
|
||||||
(closed)="showEditGoalModal.set(false)" />
|
|
||||||
}
|
}
|
||||||
@if (showEditBenchmarkModal()) {
|
@if (showEditBenchmarkModal()) {
|
||||||
<app-edit-benchmark-modal [studentId]="studentId()!" [benchmark]="showEditBenchmarkModal()!"
|
<app-edit-benchmark-modal [studentId]="studentId()!" [benchmark]="showEditBenchmarkModal()!"
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
|||||||
import { StudentGoalItem } from '../../../shared/classes/student-goal';
|
import { StudentGoalItem } from '../../../shared/classes/student-goal';
|
||||||
import { BenchmarkDto } from '../../../shared/classes/benchmark.dto';
|
import { BenchmarkDto } from '../../../shared/classes/benchmark.dto';
|
||||||
import { ProgressEventDto } from '../../../shared/classes/progress-event.dto';
|
import { ProgressEventDto } from '../../../shared/classes/progress-event.dto';
|
||||||
import { EditGoalModal } from '../edit-goal-modal/edit-goal-modal';
|
import { GoalModal } from '../goal-modal/goal-modal';
|
||||||
import { EditBenchmarkModal } from '../edit-benchmark-modal/edit-benchmark-modal';
|
import { EditBenchmarkModal } from '../edit-benchmark-modal/edit-benchmark-modal';
|
||||||
import { EditEventModal } from '../edit-event-modal/edit-event-modal';
|
import { EditEventModal } from '../edit-event-modal/edit-event-modal';
|
||||||
import { AddGoalModal } from '../add-goal-modal/add-goal-modal';
|
|
||||||
|
|
||||||
type TabView = 'benchmarks' | 'progress';
|
type TabView = 'benchmarks' | 'progress';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-workspace',
|
selector: 'app-workspace',
|
||||||
imports: [EditGoalModal, EditBenchmarkModal, EditEventModal, AddGoalModal],
|
imports: [GoalModal, EditBenchmarkModal, EditEventModal],
|
||||||
templateUrl: './workspace.html',
|
templateUrl: './workspace.html',
|
||||||
styleUrl: './workspace.scss',
|
styleUrl: './workspace.scss',
|
||||||
})
|
})
|
||||||
@@ -67,8 +66,7 @@ export class Workspace {
|
|||||||
protected readonly activeTab = signal<TabView>('benchmarks');
|
protected readonly activeTab = signal<TabView>('benchmarks');
|
||||||
|
|
||||||
// Modal states
|
// Modal states
|
||||||
protected readonly showAddGoalModal = signal(false);
|
protected readonly showGoalModal = signal<StudentGoalItem | 'add' | null>(null);
|
||||||
protected readonly showEditGoalModal = signal(false);
|
|
||||||
protected readonly showEditBenchmarkModal = signal<BenchmarkDto | null>(null);
|
protected readonly showEditBenchmarkModal = signal<BenchmarkDto | null>(null);
|
||||||
protected readonly showEditEventModal = signal<ProgressEventDto | null | 'new'>(null);
|
protected readonly showEditEventModal = signal<ProgressEventDto | null | 'new'>(null);
|
||||||
|
|
||||||
@@ -113,20 +111,20 @@ export class Workspace {
|
|||||||
|
|
||||||
// Modal handlers
|
// Modal handlers
|
||||||
onEditGoal() {
|
onEditGoal() {
|
||||||
this.showEditGoalModal.set(true);
|
this.showGoalModal.set(this.selectedGoal()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditGoalSaved() {
|
onGoalSaved() {
|
||||||
this.showEditGoalModal.set(false);
|
this.showGoalModal.set(null);
|
||||||
this.loadStudentData(this.studentId()!);
|
this.loadStudentData(this.studentId()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddGoal() {
|
onAddGoal() {
|
||||||
this.showAddGoalModal.set(true);
|
this.showGoalModal.set('add');
|
||||||
}
|
}
|
||||||
|
|
||||||
onGoalCreated(goal: StudentGoalItem) {
|
onGoalCreated(goal: StudentGoalItem) {
|
||||||
this.showAddGoalModal.set(false);
|
this.showGoalModal.set(null);
|
||||||
this.studentService.notifyDataChanged();
|
this.studentService.notifyDataChanged();
|
||||||
this.loadStudentData(this.studentId()!).then(() => {
|
this.loadStudentData(this.studentId()!).then(() => {
|
||||||
this.selectedGoalId.set(goal.goalId);
|
this.selectedGoalId.set(goal.goalId);
|
||||||
|
|||||||
Reference in New Issue
Block a user