mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 02:57:36 +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: [
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<button class="back-btn" (click)="onBack()">← Back</button>
|
||||
<span class="student-name">{{ studentIdentifier() }}</span>
|
||||
</div>
|
||||
<h2 class="goal-title">{{ goalTitle() }}</h2>
|
||||
<h2 class="goal-title">{{ goalCategory() }}</h2>
|
||||
|
||||
<!-- Error message -->
|
||||
@if (error()) {
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ export class AddProgressEvent {
|
||||
// ************************** Constructor **************************
|
||||
|
||||
constructor() {
|
||||
this.goalTitle.set(this.route.snapshot.queryParamMap.get('goalTitle') ?? '');
|
||||
this.goalCategory.set(this.route.snapshot.queryParamMap.get('goalCategory') ?? '');
|
||||
this.studentIdentifier.set(this.route.snapshot.queryParamMap.get('studentIdentifier') ?? '');
|
||||
this.studentId = this.route.snapshot.paramMap.get('studentId') ?? '';
|
||||
this.goalId = this.route.snapshot.paramMap.get('goalId') ?? '';
|
||||
@@ -32,7 +32,7 @@ export class AddProgressEvent {
|
||||
private readonly studentId: string;
|
||||
private readonly goalId: string;
|
||||
|
||||
protected readonly goalTitle = signal('');
|
||||
protected readonly goalCategory = signal('');
|
||||
protected readonly studentIdentifier = signal('');
|
||||
protected readonly notes = signal('');
|
||||
protected readonly error = signal<string | null>(null);
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<h2 class="section-heading">Goals</h2>
|
||||
<div class="goal-cards">
|
||||
@for (goal of data()?.goals; track goal.goalId) {
|
||||
<div class="goal-card" (click)="onGoalClick(goal.goalId, goal.title)">
|
||||
<span class="goal-title">{{ goal.title }}</span>
|
||||
<div class="goal-card" (click)="onGoalClick(goal.goalId, goal.category)">
|
||||
<span class="goal-title">{{ goal.category }}</span>
|
||||
<span class="event-count">{{ goal.progressEventCount }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ export class StudentGoals {
|
||||
// *****************************************************************
|
||||
// Navigates to the add-progress-event page for the selected goal.
|
||||
// *****************************************************************
|
||||
onGoalClick(goalId: string, goalTitle: string) {
|
||||
onGoalClick(goalId: string, goalCategory: string) {
|
||||
this.router.navigate(
|
||||
['students', this.studentId, 'goals', goalId, 'add-event'],
|
||||
{ queryParams: { goalTitle, studentIdentifier: this.data()?.studentIdentifier } },
|
||||
{ queryParams: { goalCategory, studentIdentifier: this.data()?.studentIdentifier } },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface StudentBenchmarkSummary {
|
||||
export interface BenchmarkDto {
|
||||
benchmarkId: string;
|
||||
goalId: string;
|
||||
goalTitle: string;
|
||||
goalCategory: string;
|
||||
benchmark: string;
|
||||
createdByName: string;
|
||||
createdAt: Date;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export interface CreateGoalDto {
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
baseline: string;
|
||||
goalParentId: string | null;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ export interface StudentGoalSummary {
|
||||
export interface StudentGoalItem {
|
||||
goalId: string; // goal.id_goal — char(36)
|
||||
goalParentId: string | null;
|
||||
title: string; // goal.title — varchar(255)
|
||||
description: string; // goal.description — text
|
||||
category: string; // goal.category — varchar(100)
|
||||
baseline: string; // goal.baseline — text
|
||||
progressEventCount: number; // count of progress_event rows for this goal
|
||||
benchmarkCount: number; // count of benchmark rows for this goal
|
||||
}
|
||||
|
||||
@@ -19,41 +19,41 @@ export class DummyStudentService {
|
||||
'1': {
|
||||
studentIdentifier: 'J.B',
|
||||
goals: [
|
||||
{ goalId: 'g1', goalParentId: null, title: 'Improve reading comprehension', description: 'Work on main-idea identification and inference skills across fiction and nonfiction texts.', category: 'Academics', progressEventCount: 5, benchmarkCount: 2 },
|
||||
{ goalId: 'g2', goalParentId: null, title: 'Complete algebra module', description: 'Finish all units in the algebra course including linear equations and graphing.', category: 'Academics', progressEventCount: 2, benchmarkCount: 0 },
|
||||
{ goalId: 'g3', goalParentId: null, title: 'Weekly journal entries', description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', progressEventCount: 8, benchmarkCount: 1 },
|
||||
{ goalId: 'g1', goalParentId: null, description: 'Work on main-idea identification and inference skills across fiction and nonfiction texts.', category: 'Academics', baseline: '', progressEventCount: 5, benchmarkCount: 2 },
|
||||
{ goalId: 'g2', goalParentId: null, description: 'Finish all units in the algebra course including linear equations and graphing.', category: 'Academics', baseline: '', progressEventCount: 2, benchmarkCount: 0 },
|
||||
{ goalId: 'g3', goalParentId: null, description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', baseline: '', progressEventCount: 8, benchmarkCount: 1 },
|
||||
],
|
||||
},
|
||||
'2': {
|
||||
studentIdentifier: 'M.K',
|
||||
goals: [
|
||||
{ goalId: 'g4', goalParentId: null, title: 'Pass certification exam', description: 'Prepare for and pass the industry certification exam by end of quarter.', category: 'Career Readiness', progressEventCount: 3, benchmarkCount: 0 },
|
||||
{ goalId: 'g5', goalParentId: null, title: 'Attendance above 90%', description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', progressEventCount: 0, benchmarkCount: 0 },
|
||||
{ goalId: 'g6', goalParentId: null, title: 'Complete internship hours', description: 'Log the required 40 hours at the assigned internship site.', category: 'Career Readiness', progressEventCount: 12, benchmarkCount: 0 },
|
||||
{ goalId: 'g7', goalParentId: null, title: 'Portfolio project', description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', progressEventCount: 1, benchmarkCount: 0 },
|
||||
{ goalId: 'g4', goalParentId: null, description: 'Prepare for and pass the industry certification exam by end of quarter.', category: 'Career Readiness', baseline: '', progressEventCount: 3, benchmarkCount: 0 },
|
||||
{ goalId: 'g5', goalParentId: null, description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', baseline: '', progressEventCount: 0, benchmarkCount: 0 },
|
||||
{ goalId: 'g6', goalParentId: null, description: 'Log the required 40 hours at the assigned internship site.', category: 'Career Readiness', baseline: '', progressEventCount: 12, benchmarkCount: 0 },
|
||||
{ goalId: 'g7', goalParentId: null, description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', baseline: '', progressEventCount: 1, benchmarkCount: 0 },
|
||||
],
|
||||
},
|
||||
'3': {
|
||||
studentIdentifier: 'A.R',
|
||||
goals: [
|
||||
{ goalId: 'g8', goalParentId: null, title: 'GED preparation', description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', progressEventCount: 6, benchmarkCount: 0 },
|
||||
{ goalId: 'g9', goalParentId: null, title: 'Resume workshop', description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', progressEventCount: 0, benchmarkCount: 0 },
|
||||
{ goalId: 'g8', goalParentId: null, description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', baseline: '', progressEventCount: 6, benchmarkCount: 0 },
|
||||
{ goalId: 'g9', goalParentId: null, description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', baseline: '', progressEventCount: 0, benchmarkCount: 0 },
|
||||
],
|
||||
},
|
||||
'4': {
|
||||
studentIdentifier: 'T.W',
|
||||
goals: [
|
||||
{ goalId: 'g10', goalParentId: null, title: 'Public speaking practice', description: 'Present in front of the class at least once per month.', category: 'Communication', progressEventCount: 4, benchmarkCount: 0 },
|
||||
{ goalId: 'g11', goalParentId: null, title: 'Math placement improvement', description: 'Move up one placement level in math by the end of the semester.', category: 'Academics', progressEventCount: 7, benchmarkCount: 0 },
|
||||
{ goalId: 'g12', goalParentId: null, title: 'Conflict resolution strategies', description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', progressEventCount: 2, benchmarkCount: 0 },
|
||||
{ goalId: 'g13', goalParentId: null, title: 'Daily attendance streak', description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', progressEventCount: 0, benchmarkCount: 0 },
|
||||
{ goalId: 'g14', goalParentId: null, title: 'Job shadow experience', description: 'Complete a job shadow day in a field of interest.', category: 'Career Readiness', progressEventCount: 1, benchmarkCount: 0 },
|
||||
{ goalId: 'g10', goalParentId: null, description: 'Present in front of the class at least once per month.', category: 'Communication', baseline: '', progressEventCount: 4, benchmarkCount: 0 },
|
||||
{ goalId: 'g11', goalParentId: null, description: 'Move up one placement level in math by the end of the semester.', category: 'Academics', baseline: '', progressEventCount: 7, benchmarkCount: 0 },
|
||||
{ goalId: 'g12', goalParentId: null, description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', baseline: '', progressEventCount: 2, benchmarkCount: 0 },
|
||||
{ goalId: 'g13', goalParentId: null, description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', baseline: '', progressEventCount: 0, benchmarkCount: 0 },
|
||||
{ goalId: 'g14', goalParentId: null, description: 'Complete a job shadow day in a field of interest.', category: 'Career Readiness', baseline: '', progressEventCount: 1, benchmarkCount: 0 },
|
||||
],
|
||||
},
|
||||
'5': {
|
||||
studentIdentifier: 'L.C',
|
||||
goals: [
|
||||
{ goalId: 'g15', goalParentId: null, title: 'Improve typing speed', description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', progressEventCount: 3, benchmarkCount: 0 },
|
||||
{ goalId: 'g15', goalParentId: null, description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', baseline: '', progressEventCount: 3, benchmarkCount: 0 },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -116,9 +116,9 @@ export class DummyStudentService {
|
||||
const newGoal: StudentGoalItem = {
|
||||
goalId: `g${Date.now()}`,
|
||||
goalParentId: null,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
category: data.category,
|
||||
baseline: data.baseline,
|
||||
progressEventCount: 0,
|
||||
benchmarkCount: 0,
|
||||
};
|
||||
@@ -174,9 +174,9 @@ export class DummyStudentService {
|
||||
}
|
||||
|
||||
const benchmarks: BenchmarkDto[] = [
|
||||
{ benchmarkId: 'bm1', goalId: 'g1', goalTitle: 'Improve reading comprehension', benchmark: 'Student will identify the main idea of a grade-level nonfiction passage with 80% accuracy.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-15'), updatedAt: null },
|
||||
{ benchmarkId: 'bm2', goalId: 'g1', goalTitle: 'Improve reading comprehension', benchmark: 'Student will make at least two supported inferences per reading session.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-16'), updatedAt: new Date('2026-02-20') },
|
||||
{ benchmarkId: 'bm3', goalId: 'g3', goalTitle: 'Weekly journal entries', benchmark: 'Student will complete a minimum of one paragraph (5 sentences) per journal entry.', createdByName: 'John Doe', createdAt: new Date('2026-02-18'), updatedAt: null },
|
||||
{ benchmarkId: 'bm1', goalId: 'g1', goalCategory: 'Academics', benchmark: 'Student will identify the main idea of a grade-level nonfiction passage with 80% accuracy.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-15'), updatedAt: null },
|
||||
{ benchmarkId: 'bm2', goalId: 'g1', goalCategory: 'Academics', benchmark: 'Student will make at least two supported inferences per reading session.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-16'), updatedAt: new Date('2026-02-20') },
|
||||
{ benchmarkId: 'bm3', goalId: 'g3', goalCategory: 'Communication', benchmark: 'Student will complete a minimum of one paragraph (5 sentences) per journal entry.', createdByName: 'John Doe', createdAt: new Date('2026-02-18'), updatedAt: null },
|
||||
];
|
||||
|
||||
return ApiResult.ok({
|
||||
|
||||
@@ -216,9 +216,9 @@ export class StudentService {
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Updates a goal's title, description, and category.
|
||||
// Updates a goal's description, category, and baseline.
|
||||
// *****************************************************************
|
||||
async updateGoal(studentId: string, goalId: string, data: { title?: string; description?: string; category?: string }): Promise<ApiResult<any>> {
|
||||
async updateGoal(studentId: string, goalId: string, data: { description?: string; category?: string; baseline?: string }): Promise<ApiResult<any>> {
|
||||
try {
|
||||
const result = await firstValueFrom(
|
||||
this.http.put<ResponseResult<any>>(`${this.base}/api/Student/${studentId}/goals/${goalId}`, data)
|
||||
|
||||
Reference in New Issue
Block a user