mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 00:38:44 +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">
|
||||
<label class="field-label">Category</label>
|
||||
<input class="field-input" type="text" [(ngModel)]="form.category"
|
||||
@@ -24,9 +24,9 @@
|
||||
}
|
||||
|
||||
<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()">
|
||||
{{ isSubmitting() ? 'Saving...' : 'Add Goal' }}
|
||||
{{ isSubmitting() ? 'Saving...' : submitLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
} @else {
|
||||
<!-- Modals -->
|
||||
@if (showAddGoalModal()) {
|
||||
<app-add-goal-modal [studentId]="studentId()!" [existingGoals]="goals()" [nextIepDate]="nextIepDate()"
|
||||
(goalCreated)="onGoalCreated($event)" (cancelled)="showAddGoalModal.set(false)" />
|
||||
}
|
||||
@if (showEditGoalModal() && selectedGoal()) {
|
||||
<app-edit-goal-modal [studentId]="studentId()!" [goal]="selectedGoal()!" (saved)="onEditGoalSaved()"
|
||||
(closed)="showEditGoalModal.set(false)" />
|
||||
@if (showGoalModal()) {
|
||||
<app-goal-modal [studentId]="studentId()!"
|
||||
[goal]="showGoalModal() === 'add' ? null : $any(showGoalModal())"
|
||||
[nextIepDate]="nextIepDate()"
|
||||
(goalCreated)="onGoalCreated($event)" (saved)="onGoalSaved()"
|
||||
(closed)="showGoalModal.set(null)" />
|
||||
}
|
||||
@if (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 { BenchmarkDto } from '../../../shared/classes/benchmark.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 { EditEventModal } from '../edit-event-modal/edit-event-modal';
|
||||
import { AddGoalModal } from '../add-goal-modal/add-goal-modal';
|
||||
|
||||
type TabView = 'benchmarks' | 'progress';
|
||||
|
||||
@Component({
|
||||
selector: 'app-workspace',
|
||||
imports: [EditGoalModal, EditBenchmarkModal, EditEventModal, AddGoalModal],
|
||||
imports: [GoalModal, EditBenchmarkModal, EditEventModal],
|
||||
templateUrl: './workspace.html',
|
||||
styleUrl: './workspace.scss',
|
||||
})
|
||||
@@ -67,8 +66,7 @@ export class Workspace {
|
||||
protected readonly activeTab = signal<TabView>('benchmarks');
|
||||
|
||||
// Modal states
|
||||
protected readonly showAddGoalModal = signal(false);
|
||||
protected readonly showEditGoalModal = signal(false);
|
||||
protected readonly showGoalModal = signal<StudentGoalItem | 'add' | null>(null);
|
||||
protected readonly showEditBenchmarkModal = signal<BenchmarkDto | null>(null);
|
||||
protected readonly showEditEventModal = signal<ProgressEventDto | null | 'new'>(null);
|
||||
|
||||
@@ -113,20 +111,20 @@ export class Workspace {
|
||||
|
||||
// Modal handlers
|
||||
onEditGoal() {
|
||||
this.showEditGoalModal.set(true);
|
||||
this.showGoalModal.set(this.selectedGoal()!);
|
||||
}
|
||||
|
||||
onEditGoalSaved() {
|
||||
this.showEditGoalModal.set(false);
|
||||
onGoalSaved() {
|
||||
this.showGoalModal.set(null);
|
||||
this.loadStudentData(this.studentId()!);
|
||||
}
|
||||
|
||||
onAddGoal() {
|
||||
this.showAddGoalModal.set(true);
|
||||
this.showGoalModal.set('add');
|
||||
}
|
||||
|
||||
onGoalCreated(goal: StudentGoalItem) {
|
||||
this.showAddGoalModal.set(false);
|
||||
this.showGoalModal.set(null);
|
||||
this.studentService.notifyDataChanged();
|
||||
this.loadStudentData(this.studentId()!).then(() => {
|
||||
this.selectedGoalId.set(goal.goalId);
|
||||
|
||||
Reference in New Issue
Block a user