Added Goals fields

This commit is contained in:
ivan-pelly
2026-03-15 09:35:58 -07:00
parent 242b1bce27
commit 53d0539d28
66 changed files with 1322 additions and 329 deletions
@@ -0,0 +1,4 @@
<div class="tile" [class.checked]="checked()" (click)="onTap()">
<span class="check-indicator">{{ checked() ? '✓' : '' }}</span>
<span class="tile-label">{{ label() }}</span>
</div>
@@ -0,0 +1,46 @@
.tile {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
border: 1px solid #ddd;
border-radius: 8px;
background: #fff;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.tile:active {
background: #f5f5f5;
}
.tile.checked {
border-color: #4f46e5;
background: #eef2ff;
}
.check-indicator {
display: flex;
align-items: center;
justify-content: center;
width: 1.375rem;
height: 1.375rem;
border: 2px solid #ccc;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 700;
color: #fff;
flex-shrink: 0;
}
.tile.checked .check-indicator {
background: #4f46e5;
border-color: #4f46e5;
}
.tile-label {
font-size: 0.9375rem;
color: #333;
line-height: 1.3;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ToggleBenchmark } from './toggle-benchmark';
describe('ToggleBenchmark', () => {
let component: ToggleBenchmark;
let fixture: ComponentFixture<ToggleBenchmark>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ToggleBenchmark]
})
.compileComponents();
fixture = TestBed.createComponent(ToggleBenchmark);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,22 @@
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-toggle-benchmark',
imports: [],
templateUrl: './toggle-benchmark.html',
styleUrl: './toggle-benchmark.scss',
})
export class ToggleBenchmark {
// ************************** Declarations *************************
readonly label = input.required<string>();
readonly checked = input.required<boolean>();
readonly toggled = output<void>();
// ************************ Event Handlers *************************
onTap() {
this.toggled.emit();
}
}
@@ -15,11 +15,20 @@
<div class="form-card">
<label class="field-label">Progress event notes</label>
<textarea class="notes-input" placeholder="Type your message here." rows="5" [(ngModel)]="notes"></textarea>
<label class="field-label">Voice note</label>
<button class="voice-btn">🎙 Record voice note</button>
</div>
@if (benchmarkItems().length > 0) {
<label class="field-label">Associated Benchmarks</label>
<div class="benchmark-scroll">
@for (bm of benchmarkItems(); track bm.benchmarkId) {
<app-toggle-benchmark
[label]="bm.label"
[checked]="bm.checked"
(toggled)="onToggleBenchmark(bm.benchmarkId)" />
}
</div>
}
<!-- Save button -->
<button class="save-btn" [disabled]="!canSave()" (click)="onSave()">
{{ saving() ? 'Saving...' : 'Save' }}
@@ -129,4 +129,15 @@
color: #dc2626;
border-radius: 8px;
font-size: 0.875rem;
}
/* Scrollable benchmark region */
.benchmark-scroll {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex: 1;
overflow-y: auto;
min-height: 0;
margin-bottom: 1rem;
}
@@ -3,12 +3,19 @@ import { ActivatedRoute, Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { HttpErrorResponse } from '@angular/common/http';
import { describeHttpError } from '../../../shared/classes/http-errors';
import { DummyStudentService } from '../../../shared/services/dummy-student.service';
import { StudentService } from '../../../shared/services/student.service';
interface BenchmarkCheckItem {
benchmarkId: string;
label: string;
checked: boolean;
}
import { ToggleBenchmark } from '../../components/toggle-benchmark/toggle-benchmark';
@Component({
selector: 'app-add-progress-event',
imports: [FormsModule],
imports: [FormsModule, ToggleBenchmark],
templateUrl: './add-progress-event.html',
styleUrl: './add-progress-event.scss',
})
@@ -21,6 +28,7 @@ export class AddProgressEvent {
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') ?? '';
this.loadBenchmarks();
}
// ************************** Declarations *************************
@@ -37,6 +45,7 @@ export class AddProgressEvent {
protected readonly notes = signal('');
protected readonly error = signal<string | null>(null);
protected readonly saving = signal(false);
protected readonly benchmarkItems = signal<BenchmarkCheckItem[]>([]);
// ************************** Properties ***************************
@@ -59,15 +68,31 @@ export class AddProgressEvent {
}
// *****************************************************************
// Saves the progress event. On success, returns to the goal list.
// On failure, displays the error message from the API.
// Toggles a benchmark checkbox.
// *****************************************************************
onToggleBenchmark(benchmarkId: string) {
this.benchmarkItems.update(items =>
items.map(b => b.benchmarkId === benchmarkId ? { ...b, checked: !b.checked } : b)
);
}
// *****************************************************************
// Saves the progress event with optional benchmark associations.
// On success, returns to the goal list.
// *****************************************************************
async onSave() {
this.error.set(null);
this.saving.set(true);
const checkedIds = this.benchmarkItems()
.filter(b => b.checked)
.map(b => b.benchmarkId);
try {
const result = await this.studentService.addProgressEvent(this.studentId, this.goalId, this.notes().trim());
const result = await this.studentService.addProgressEvent(
this.studentId, this.goalId, this.notes().trim(),
checkedIds.length > 0 ? checkedIds : undefined
);
this.saving.set(false);
if (result.success) {
this.router.navigate(['students', this.studentId, 'goals']);
@@ -81,4 +106,19 @@ export class AddProgressEvent {
}
// ********************** Support Procedures ***********************
// *****************************************************************
// Loads benchmarks for the current goal to populate checkboxes.
// *****************************************************************
private async loadBenchmarks() {
const result = await this.studentService.getBenchmarksForStudent(this.studentId);
if (result.success && result.payload) {
const goalBenchmarks = result.payload.benchmarks.filter(b => b.goalId === this.goalId);
this.benchmarkItems.set(goalBenchmarks.map(b => ({
benchmarkId: b.benchmarkId,
label: b.shortName || b.benchmark,
checked: false,
})));
}
}
}