mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 13:27:35 +00:00
Renamed some of the goal fields to align with business logic
This commit is contained in:
+23
-24
@@ -8,18 +8,6 @@
|
||||
|
||||
<form class="modal-body" (ngSubmit)="onSubmit()" #goalForm="ngForm">
|
||||
|
||||
<div class="field">
|
||||
<label for="title">Title<span class="required">*</span></label>
|
||||
<input
|
||||
id="title"
|
||||
type="text"
|
||||
[(ngModel)]="form.title"
|
||||
name="title"
|
||||
required
|
||||
placeholder="e.g. Improve reading comprehension"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="category">Category<span class="required">*</span></label>
|
||||
<input
|
||||
@@ -32,18 +20,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if (parentGoalOptions().length > 0) {
|
||||
<div class="field">
|
||||
<label for="goalParentId">Parent Goal <span class="optional">(optional)</span></label>
|
||||
<select id="goalParentId" [(ngModel)]="form.goalParentId" name="goalParentId">
|
||||
<option [ngValue]="null">None</option>
|
||||
@for (goal of parentGoalOptions(); track goal.goalId) {
|
||||
<option [ngValue]="goal.goalId">{{ goal.title }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="field">
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
@@ -55,6 +31,29 @@
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="baseline">Baseline</label>
|
||||
<textarea
|
||||
id="baseline"
|
||||
[(ngModel)]="form.baseline"
|
||||
name="baseline"
|
||||
rows="3"
|
||||
placeholder="Enter baseline..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
@if (parentGoalOptions().length > 0) {
|
||||
<div class="field">
|
||||
<label for="goalParentId">Parent Goal <span class="optional">(optional)</span></label>
|
||||
<select id="goalParentId" [(ngModel)]="form.goalParentId" name="goalParentId">
|
||||
<option [ngValue]="null">None</option>
|
||||
@for (goal of parentGoalOptions(); track goal.goalId) {
|
||||
<option [ngValue]="goal.goalId">{{ goal.category }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (errorMessage()) {
|
||||
<p class="error">{{ errorMessage() }}</p>
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,9 +31,9 @@ export class AddGoalModal {
|
||||
);
|
||||
|
||||
protected form: CreateGoalDto = {
|
||||
title: '',
|
||||
description: '',
|
||||
category: '',
|
||||
baseline: '',
|
||||
goalParentId: null,
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -9,14 +9,14 @@
|
||||
<form class="modal-body" (ngSubmit)="onSubmit()" #studentForm="ngForm">
|
||||
|
||||
<div class="field">
|
||||
<label for="identifier">Identifier</label>
|
||||
<label for="identifier">Student</label>
|
||||
<input
|
||||
id="identifier"
|
||||
type="text"
|
||||
[(ngModel)]="form.identifier"
|
||||
name="identifier"
|
||||
required
|
||||
placeholder="e.g. Student123"
|
||||
placeholder="Initials or other non-personally identifiable label"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
<div class="detail-card">
|
||||
<div class="field">
|
||||
<span class="field-label">Goal</span>
|
||||
<span class="field-value">{{ goalTitle }}</span>
|
||||
<span class="field-value">{{ goalCategory }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label" for="benchmarkText">Benchmark</label>
|
||||
|
||||
+6
-6
@@ -47,7 +47,7 @@ export class BenchmarkCardFull implements OnDestroy {
|
||||
private savedBenchmarkText = '';
|
||||
|
||||
// Read-only metadata
|
||||
protected goalTitle = '';
|
||||
protected goalCategory = '';
|
||||
protected createdByName = '';
|
||||
protected createdAt: Date | null = null;
|
||||
protected updatedAt: Date | null = null;
|
||||
@@ -127,7 +127,7 @@ export class BenchmarkCardFull implements OnDestroy {
|
||||
this.isNew.set(true);
|
||||
this.benchmarkText = '';
|
||||
this.savedBenchmarkText = '';
|
||||
this.loadGoalTitle();
|
||||
this.loadGoalCategory();
|
||||
this.loaded.set(true);
|
||||
return;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ export class BenchmarkCardFull implements OnDestroy {
|
||||
|
||||
this.benchmarkText = bm.benchmark;
|
||||
this.savedBenchmarkText = bm.benchmark;
|
||||
this.goalTitle = bm.goalTitle;
|
||||
this.goalCategory = bm.goalCategory;
|
||||
this.createdByName = bm.createdByName;
|
||||
this.createdAt = bm.createdAt;
|
||||
this.updatedAt = bm.updatedAt;
|
||||
@@ -156,13 +156,13 @@ export class BenchmarkCardFull implements OnDestroy {
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Loads the goal title for a new benchmark.
|
||||
// Loads the goal category for a new benchmark.
|
||||
// *****************************************************************
|
||||
private loadGoalTitle() {
|
||||
private loadGoalCategory() {
|
||||
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||
if (result.success && result.payload) {
|
||||
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||
this.goalTitle = goal?.title ?? '';
|
||||
this.goalCategory = goal?.category ?? '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="goal-badge">{{ benchmark().goalTitle }}</span>
|
||||
<span class="goal-badge">{{ benchmark().goalCategory }}</span>
|
||||
@if (benchmark().updatedAt) {
|
||||
<span class="date">Updated: {{ benchmark().updatedAt | date:'M/d/yy' }}</span>
|
||||
} @else {
|
||||
|
||||
+9
-4
@@ -4,8 +4,10 @@
|
||||
<button class="toolbar-btn" (click)="onAddBenchmark()">+ Add a Benchmark</button>
|
||||
</div>
|
||||
|
||||
@if (studentIdentifier()) {
|
||||
<h2 class="section-header">Benchmarks for {{ studentIdentifier() }}</h2>
|
||||
@if (goalCategory()) {
|
||||
<h2 class="section-header">Benchmarks for {{ goalCategory() }}</h2>
|
||||
} @else {
|
||||
<h2 class="section-header">Benchmarks</h2>
|
||||
}
|
||||
|
||||
@if (errorMessage()) {
|
||||
@@ -13,8 +15,11 @@
|
||||
}
|
||||
|
||||
@if (benchmarks().length === 0 && !errorMessage()) {
|
||||
<p class="empty-state">No benchmarks yet. Click <a class="empty-link" (click)="onAddBenchmark()">Add a Benchmark</a> to
|
||||
get started.</p>
|
||||
<div class="empty-state">
|
||||
<p>Benchmarks are milestones on the way to achieving a goal. They are optional in this system.</p>
|
||||
<p>No benchmarks yet.</p>
|
||||
<p>Click <strong>+ Add a Benchmark</strong> in the upper right to get started.</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="card-grid">
|
||||
@for (bm of benchmarks(); track bm.benchmarkId) {
|
||||
|
||||
+6
-9
@@ -54,16 +54,13 @@
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: #888;
|
||||
font-size: 0.9375rem;
|
||||
margin: 2rem auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-link {
|
||||
color: #4f46e5;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
padding: 3rem 1.5rem;
|
||||
color: #555;
|
||||
font-size: 0.9375rem;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
|
||||
+35
-5
@@ -18,6 +18,7 @@ export class BenchmarkList {
|
||||
this.studentId = this.route.snapshot.paramMap.get('studentId')!;
|
||||
this.goalId = this.route.snapshot.paramMap.get('goalId') || '';
|
||||
this.loadBenchmarks();
|
||||
this.loadGoalCategory();
|
||||
}
|
||||
|
||||
// ************************** Declarations *************************
|
||||
@@ -28,7 +29,7 @@ export class BenchmarkList {
|
||||
|
||||
protected readonly studentId: string;
|
||||
protected readonly goalId: string;
|
||||
protected readonly studentIdentifier = signal<string | null>(null);
|
||||
protected readonly goalCategory = signal<string | null>(null);
|
||||
protected readonly benchmarks = signal<BenchmarkDto[]>([]);
|
||||
protected readonly errorMessage = signal<string | null>(null);
|
||||
|
||||
@@ -43,22 +44,51 @@ export class BenchmarkList {
|
||||
}
|
||||
|
||||
onBack() {
|
||||
this.router.navigate(['/students', this.studentId, 'goals', this.goalId]);
|
||||
if (this.goalId) {
|
||||
this.router.navigate(['/students', this.studentId, 'goals', this.goalId]);
|
||||
} else {
|
||||
this.router.navigate(['/students', this.studentId, 'goals']);
|
||||
}
|
||||
}
|
||||
|
||||
// ********************** Support Procedures ***********************
|
||||
|
||||
// *****************************************************************
|
||||
// Loads benchmarks for the student from the service.
|
||||
// Loads benchmarks for the student from the service. Also sets
|
||||
// goalCategory from the first benchmark's data.
|
||||
// *****************************************************************
|
||||
private loadBenchmarks() {
|
||||
this.studentService.getBenchmarksForStudent(this.studentId).then(data => {
|
||||
if (!data.success) {
|
||||
this.errorMessage.set(data.message);
|
||||
} else {
|
||||
this.studentIdentifier.set(data.payload?.studentIdentifier ?? null);
|
||||
this.benchmarks.set(data.payload?.benchmarks ?? []);
|
||||
const benchmarks = data.payload?.benchmarks ?? [];
|
||||
this.benchmarks.set(benchmarks);
|
||||
|
||||
// Set the goal category from benchmark data if available
|
||||
if (this.goalId && benchmarks.length > 0) {
|
||||
const match = benchmarks.find(b => b.goalId === this.goalId);
|
||||
if (match) this.goalCategory.set(match.goalCategory);
|
||||
}
|
||||
|
||||
// If we still don't have a category, load it from the goals API
|
||||
if (!this.goalCategory()) {
|
||||
this.loadGoalCategory();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Loads the goal category from the goals API as a fallback when
|
||||
// no benchmarks exist yet to extract it from.
|
||||
// *****************************************************************
|
||||
private loadGoalCategory() {
|
||||
if (!this.goalId) return;
|
||||
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||
if (!result.success || !result.payload) return;
|
||||
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||
this.goalCategory.set(goal?.category ?? null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
<div class="toolbar">
|
||||
<button class="toolbar-btn back-btn" (click)="onBack()">↑ Student</button>
|
||||
<button class="toolbar-btn back-btn" (click)="onBack()">↑ Goals</button>
|
||||
<span class="toolbar-title">Goal Detail</span>
|
||||
<span class="spacer"></span>
|
||||
</div>
|
||||
@@ -15,8 +15,8 @@
|
||||
@if (loaded()) {
|
||||
<div class="detail-card">
|
||||
<div class="field">
|
||||
<label class="field-label" for="title">Title</label>
|
||||
<input id="title" class="field-input" type="text" [(ngModel)]="title" />
|
||||
<label class="field-label" for="baseline">Baseline</label>
|
||||
<input id="baseline" class="field-input" type="text" [(ngModel)]="baseline" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label" for="description">Description</label>
|
||||
|
||||
+11
-11
@@ -39,18 +39,18 @@ export class GoalCardFull implements OnDestroy {
|
||||
protected readonly saving = signal(false);
|
||||
|
||||
// Form fields
|
||||
protected title = '';
|
||||
protected description = '';
|
||||
protected category = '';
|
||||
protected baseline = '';
|
||||
|
||||
// Read-only metadata
|
||||
protected progressEventCount = 0;
|
||||
protected benchmarkCount = 0;
|
||||
|
||||
// Snapshot
|
||||
private savedTitle = '';
|
||||
private savedDescription = '';
|
||||
private savedCategory = '';
|
||||
private savedBaseline = '';
|
||||
|
||||
// ************************** Properties ***************************
|
||||
|
||||
@@ -58,9 +58,9 @@ export class GoalCardFull implements OnDestroy {
|
||||
// Returns true if form values differ from the saved snapshot.
|
||||
// *****************************************************************
|
||||
hasChanges(): boolean {
|
||||
return this.title !== this.savedTitle
|
||||
|| this.description !== this.savedDescription
|
||||
|| this.category !== this.savedCategory;
|
||||
return this.description !== this.savedDescription
|
||||
|| this.category !== this.savedCategory
|
||||
|| this.baseline !== this.savedBaseline;
|
||||
}
|
||||
|
||||
// ************************ Public Methods *************************
|
||||
@@ -76,17 +76,17 @@ export class GoalCardFull implements OnDestroy {
|
||||
this.successMessage.set(null);
|
||||
|
||||
const result = await this.studentService.updateGoal(this.studentId, this.goalId, {
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
category: this.category,
|
||||
baseline: this.baseline,
|
||||
});
|
||||
|
||||
this.saving.set(false);
|
||||
|
||||
if (result.success) {
|
||||
this.savedTitle = this.title;
|
||||
this.savedDescription = this.description;
|
||||
this.savedCategory = this.category;
|
||||
this.savedBaseline = this.baseline;
|
||||
this.successMessage.set('Changes saved.');
|
||||
} else {
|
||||
this.errorMessage.set(result.message);
|
||||
@@ -97,15 +97,15 @@ export class GoalCardFull implements OnDestroy {
|
||||
// Reverts form fields to the last-saved snapshot.
|
||||
// *****************************************************************
|
||||
onCancel() {
|
||||
this.title = this.savedTitle;
|
||||
this.description = this.savedDescription;
|
||||
this.category = this.savedCategory;
|
||||
this.baseline = this.savedBaseline;
|
||||
this.errorMessage.set(null);
|
||||
this.successMessage.set(null);
|
||||
}
|
||||
|
||||
onBack() {
|
||||
this.router.navigate(['/students', this.studentId]);
|
||||
this.router.navigate(['/students', this.studentId, 'goals']);
|
||||
}
|
||||
|
||||
onProgressEvents() {
|
||||
@@ -139,15 +139,15 @@ export class GoalCardFull implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
this.title = goal.title;
|
||||
this.description = goal.description;
|
||||
this.category = goal.category;
|
||||
this.baseline = goal.baseline;
|
||||
this.progressEventCount = goal.progressEventCount;
|
||||
this.benchmarkCount = goal.benchmarkCount;
|
||||
|
||||
this.savedTitle = goal.title;
|
||||
this.savedDescription = goal.description;
|
||||
this.savedCategory = goal.category;
|
||||
this.savedBaseline = goal.baseline;
|
||||
this.loaded.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<span class="event-count">{{ goal().progressEventCount }} events</span>
|
||||
</div>
|
||||
|
||||
<h3 class="title">{{ goal().title }}</h3>
|
||||
<h3 class="title">{{ goal().category }}</h3>
|
||||
<p class="description">{{ goal().description }}</p>
|
||||
|
||||
<div class="card-footer">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:host {
|
||||
display: block;
|
||||
width: 300px;
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.card {
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
}
|
||||
|
||||
@if (goals().length === 0 && !errorMessage()) {
|
||||
<p class="empty-state">No goals yet. Click <strong>+ Add a Goal</strong> to get started.</p>
|
||||
<div class="empty-state">
|
||||
<p>No goals yet.</p>
|
||||
<p>Click <strong>+ Add a Goal</strong> in the upper right to get started.</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="card-grid">
|
||||
@for (goal of goals(); track goal.goalId) {
|
||||
|
||||
@@ -66,10 +66,13 @@
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: #888;
|
||||
font-size: 0.9375rem;
|
||||
margin: 2rem auto;
|
||||
text-align: center;
|
||||
padding: 3rem 1.5rem;
|
||||
color: #555;
|
||||
font-size: 0.9375rem;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
@@ -88,4 +91,5 @@
|
||||
background: #fff;
|
||||
border-top: 1px solid #ddd;
|
||||
flex-shrink: 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
@@ -49,6 +49,7 @@ export class GoalList implements OnDestroy {
|
||||
onGoalCreated(goal: StudentGoalItem) {
|
||||
this.goals.update(list => [...list, goal]);
|
||||
this.showAddModal.set(false);
|
||||
this.studentService.notifyDataChanged();
|
||||
}
|
||||
|
||||
onModalCancelled() {
|
||||
|
||||
+2
-2
@@ -7,10 +7,10 @@
|
||||
|
||||
<!-- <img class="hero-image" src="/slalomcropped.png" alt="Slalom" /> -->
|
||||
|
||||
@if (studentIdentifier() && goalTitle()) {
|
||||
@if (studentIdentifier() && goalCategory()) {
|
||||
<div class="header-row">
|
||||
<h2 class="section-header">
|
||||
Student: {{ studentIdentifier() }} Goal: {{ goalTitle() }}
|
||||
Student: {{ studentIdentifier() }} Goal: {{ goalCategory() }}
|
||||
@if (isFiltered()) {
|
||||
<span class="filter-count">(showing {{ filteredEvents().length }} of {{ events().length }})</span>
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class ProgressList implements OnDestroy {
|
||||
this.studentId = this.route.snapshot.paramMap.get('studentId')!;
|
||||
this.goalId = this.route.snapshot.paramMap.get('goalId')!;
|
||||
this.loadEvents();
|
||||
this.loadGoalTitle();
|
||||
this.loadGoalCategory();
|
||||
|
||||
this.searchInput$.pipe(debounceTime(300)).subscribe(term => {
|
||||
this.searchTerm.set(term);
|
||||
@@ -38,7 +38,7 @@ export class ProgressList implements OnDestroy {
|
||||
private readonly searchInput$ = new Subject<string>();
|
||||
|
||||
protected readonly studentIdentifier = signal<string | null>(null);
|
||||
protected readonly goalTitle = signal<string | null>(null);
|
||||
protected readonly goalCategory = signal<string | null>(null);
|
||||
protected readonly events = signal<ProgressEventDto[]>([]);
|
||||
protected readonly errorMessage = signal<string | null>(null);
|
||||
protected readonly rawSearchText = signal('');
|
||||
@@ -118,16 +118,15 @@ export class ProgressList implements OnDestroy {
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Loads the goal title from the student's goal list so the heading
|
||||
// can display "Progress for <goal title>".
|
||||
// Loads the goal category from the student's goal list so the heading
|
||||
// can display "Progress for <goal category>".
|
||||
// *****************************************************************
|
||||
private loadGoalTitle() {
|
||||
private loadGoalCategory() {
|
||||
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||
if (result.success && result.payload) {
|
||||
this.studentIdentifier.set(result.payload.studentIdentifier);
|
||||
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||
this.goalTitle.set(goal?.title ?? null);
|
||||
}
|
||||
if (!result.success || !result.payload) return;
|
||||
this.studentIdentifier.set(result.payload.studentIdentifier);
|
||||
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||
this.goalCategory.set(goal?.category ?? null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ export class StudentCardList {
|
||||
onStudentCreated(student: StudentCardDto) {
|
||||
this.students.update(list => this.sortByIdentifier([...list, student]));
|
||||
this.showAddModal.set(false);
|
||||
this.studentService.notifyDataChanged();
|
||||
}
|
||||
|
||||
onModalCancelled() {
|
||||
|
||||
@@ -106,6 +106,8 @@
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
@@ -120,7 +120,7 @@ export class Home implements OnDestroy {
|
||||
if (!result.success || !result.payload) return [];
|
||||
|
||||
return result.payload.goals.map(goal => ({
|
||||
label: goal.title,
|
||||
label: goal.category,
|
||||
routerLink: ['/students', studentId, 'goals', goal.goalId],
|
||||
childCount: 2,
|
||||
children: [
|
||||
|
||||
Reference in New Issue
Block a user