diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.html b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.html index 0e89ac2..329c66c 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.html @@ -1,4 +1,4 @@ -
+
{{ goal().category }} {{ goal().progressEventCount }} events @@ -6,4 +6,4 @@

{{ goal().title }}

{{ goal().description }}

-
+
\ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.scss b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.scss index 21c6766..c3a0d8d 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.scss +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.scss @@ -11,6 +11,16 @@ display: flex; flex-direction: column; gap: 0.625rem; + height: 130px; + min-width: 0; +} + +.card.clickable { + cursor: pointer; +} + +.card.clickable:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .card-header { @@ -38,6 +48,9 @@ font-size: 1rem; font-weight: 600; color: #111; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .description { @@ -45,4 +58,8 @@ font-size: 0.875rem; color: #555; line-height: 1.5; -} + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.ts b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.ts index 9070017..abd6797 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card/goal-card.ts @@ -1,4 +1,5 @@ -import { Component, input } from '@angular/core'; +import { Component, inject, input } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; import { StudentGoalItem } from '../../../shared/classes/student-goal'; @Component({ @@ -13,6 +14,9 @@ export class GoalCard { // ************************** Declarations ************************* + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + readonly goal = input.required(); // ************************** Properties *************************** @@ -21,5 +25,13 @@ export class GoalCard { // ************************ Event Handlers ************************* + // ***************************************************************** + // Navigates to the progress events page for this goal. + // ***************************************************************** + onCardClick() { + const studentId = this.route.snapshot.paramMap.get('studentId')!; + this.router.navigate(['/students', studentId, 'goals', this.goal().goalId, 'progress']); + } + // ********************** Support Procedures *********************** } diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.html b/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.html index f38b1a8..a72299d 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.html @@ -1,31 +1,28 @@
- @if (studentIdentifier()) { - {{ studentIdentifier() }} - }
+@if (studentIdentifier()) { +

Goals for {{ studentIdentifier() }}

+} + @if (showAddModal()) { - + } @if (errorMessage()) { -

{{ errorMessage() }}

+

{{ errorMessage() }}

} @if (goals().length === 0 && !errorMessage()) { -

No goals yet. Click + Add a Goal to get started.

+

No goals yet. Click + Add a Goal to get started.

} @else { -
- @for (goal of goals(); track goal.goalId) { - - } -
-} +
+ @for (goal of goals(); track goal.goalId) { + + } +
+} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.scss b/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.scss index 592f50b..56ef332 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.scss +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.scss @@ -36,10 +36,11 @@ margin-left: 0.5rem; } -.student-label { - font-size: 0.9375rem; +.section-header { + font-size: 1.125rem; font-weight: 600; color: #333; + margin: 0 0 0.5rem; } .spacer { @@ -65,4 +66,4 @@ gap: 1rem; overflow-y: auto; flex: 1; -} +} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.html b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.html new file mode 100644 index 0000000..4cb8d0d --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.html @@ -0,0 +1,9 @@ +
+

{{ event().content }}

+
+ + +
+ {{ event().createdByName }} + {{ event().createdAt | date:'MMM d, y' }} +
\ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.scss b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.scss new file mode 100644 index 0000000..b1d74cb --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.scss @@ -0,0 +1,61 @@ +:host { + display: block; +} + +.card { + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + padding: 1.25rem 1.5rem; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: auto auto; + gap: 0.75rem 1rem; +} + +.content { + grid-column: 1; + grid-row: 1; + margin: 0; + font-size: 0.9375rem; + color: #333; + line-height: 1.6; +} + +.action-icons { + grid-column: 2; + grid-row: 1; + display: flex; + gap: 0.5rem; + align-self: start; + justify-content: flex-end; +} + +.author { + grid-column: 1; + grid-row: 2; + font-size: 0.8125rem; + font-weight: 600; + color: #888; +} + +.date { + grid-column: 2; + grid-row: 2; + font-size: 0.8125rem; + color: #888; + text-align: right; +} + +.icon-btn { + background: none; + border: none; + cursor: pointer; + font-size: 1rem; + color: #888; + padding: 0.125rem; +} + +.icon-btn:hover { + color: #4f46e5; +} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.spec.ts b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.spec.ts new file mode 100644 index 0000000..6fe4032 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProgressItem } from './progress-item'; + +describe('ProgressItem', () => { + let component: ProgressItem; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProgressItem] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProgressItem); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.ts b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.ts new file mode 100644 index 0000000..401e6f4 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.ts @@ -0,0 +1,26 @@ +import { Component, input } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { ProgressEventDto } from '../../../shared/classes/progress-event.dto'; + +@Component({ + selector: 'app-progress-item', + imports: [DatePipe], + templateUrl: './progress-item.html', + styleUrl: './progress-item.scss', +}) +export class ProgressItem { + + // ************************** Constructor ************************** + + // ************************** Declarations ************************* + + readonly event = input.required(); + + // ************************** Properties *************************** + + // ************************ Public Methods ************************* + + // ************************ Event Handlers ************************* + + // ********************** Support Procedures *********************** +} diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.html b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.html new file mode 100644 index 0000000..ffb1510 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.html @@ -0,0 +1,35 @@ +
+ +
+ +@if (studentIdentifier() && goalTitle()) { +
+

+ {{ events().length }} Progress Events for {{ studentIdentifier() }} for goal: {{ goalTitle() }} + @if (isFiltered()) { + (showing {{ filteredEvents().length }}) + } +

+ +
+} + +@if (errorMessage()) { +

{{ errorMessage() }}

+} + +@if (filteredEvents().length === 0 && !errorMessage()) { +

No progress events recorded yet.

+} @else { +
+ @for (evt of filteredEvents(); track evt.progressEventId) { + + } +
+} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.scss b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.scss new file mode 100644 index 0000000..28b1883 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.scss @@ -0,0 +1,116 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} + +.toolbar { + display: flex; + align-items: center; + gap: 0.75rem; + height: 40px; + padding-right: 0.5rem; + border-radius: 8px; + background: #fff; + border-bottom: 1px solid #ddd; + margin-bottom: 1rem; + flex-shrink: 0; +} + +.toolbar-btn { + padding: 0.375rem 0.75rem; + background: transparent; + color: #4f46e5; + border: 1px solid #4f46e5; + border-radius: 6px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; +} + +.toolbar-btn:hover { + background: #eef2ff; +} + +.back-btn { + margin-left: 0.5rem; +} + +.header-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; + padding-right: calc(0.75rem + 17px); + flex-shrink: 0; +} + +.section-header { + font-size: 1.125rem; + font-weight: 600; + color: #333; + margin: 0; +} + +.filter-count { + font-weight: 400; + color: #888; +} + +.search-box { + position: relative; +} + +.search-input { + padding: 0.375rem 2rem 0.375rem 0.75rem; + border: 1px solid #ccc; + border-radius: 6px; + font-size: 0.875rem; + width: 200px; + outline: none; +} + +.search-input:focus { + border-color: #4f46e5; +} + +.clear-btn { + position: absolute; + right: 0.375rem; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + font-size: 1.125rem; + color: #888; + line-height: 1; + padding: 0 0.125rem; +} + +.clear-btn:hover { + color: #333; +} + +.error { + font-size: 0.875rem; + color: #dc2626; + margin: 0 0 1rem; +} + +.empty-state { + color: #888; + font-size: 0.9375rem; + margin: 2rem auto; + text-align: center; +} + +.event-list { + display: flex; + flex-direction: column; + gap: 0.75rem; + overflow-y: auto; + scrollbar-gutter: stable; + flex: 1; + padding-right: 0.75rem; +} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.spec.ts b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.spec.ts new file mode 100644 index 0000000..f6d6a62 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProgressList } from './progress-list'; + +describe('ProgressList', () => { + let component: ProgressList; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProgressList] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProgressList); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.ts b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.ts new file mode 100644 index 0000000..da48b3b --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.ts @@ -0,0 +1,130 @@ +import { Component, computed, inject, signal, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; +import { ProgressItem } from '../progress-item/progress-item'; +import { ProgressEventDto } from '../../../shared/classes/progress-event.dto'; +import { DummyStudentService } from '../../../shared/services/dummy-student.service'; +import { StudentService } from '../../../shared/services/student.service'; + +@Component({ + selector: 'app-progress-list', + imports: [ProgressItem], + templateUrl: './progress-list.html', + styleUrl: './progress-list.scss', +}) +export class ProgressList implements OnDestroy { + + // ************************** Constructor ************************** + + constructor() { + this.studentId = this.route.snapshot.paramMap.get('studentId')!; + this.goalId = this.route.snapshot.paramMap.get('goalId')!; + this.loadEvents(); + this.loadGoalTitle(); + + this.searchInput$.pipe(debounceTime(300)).subscribe(term => { + this.searchTerm.set(term); + }); + } + + // ************************** Declarations ************************* + + private readonly dummyService = inject(DummyStudentService); + private readonly studentService = inject(StudentService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + + private readonly studentId: string; + private readonly goalId: string; + private readonly searchInput$ = new Subject(); + + protected readonly studentIdentifier = signal(null); + protected readonly goalTitle = signal(null); + protected readonly events = signal([]); + protected readonly errorMessage = signal(null); + protected readonly rawSearchText = signal(''); + protected readonly searchTerm = signal(''); + + // ************************** Properties *************************** + + // ***************************************************************** + // Returns events filtered by the debounced search term. Matches + // against the event content (case-insensitive). Only filters when + // the term is at least 2 characters. + // ***************************************************************** + protected readonly filteredEvents = computed(() => { + const term = this.searchTerm().trim().toLowerCase(); + if (term.length < 2) return this.events(); + return this.events().filter(e => e.content.toLowerCase().includes(term)); + }); + + protected readonly isFiltered = computed(() => { + return this.searchTerm().trim().length >= 2 && this.filteredEvents().length !== this.events().length; + }); + + // ************************ Public Methods ************************* + + // ************************ Event Handlers ************************* + + // ***************************************************************** + // Navigates back to the goals list for this student. + // ***************************************************************** + onBack() { + this.router.navigate(['/students', this.studentId, 'goals']); + } + + // ***************************************************************** + // Pushes the raw input value into the debounce stream. + // ***************************************************************** + onSearchInput(value: string) { + this.rawSearchText.set(value); + this.searchInput$.next(value); + } + + // ***************************************************************** + // Clears the search box and resets the filter. + // ***************************************************************** + onClearSearch() { + this.rawSearchText.set(''); + this.searchTerm.set(''); + } + + ngOnDestroy() { + this.searchInput$.complete(); + } + + // ********************** Support Procedures *********************** + + // ***************************************************************** + // Loads progress events for the given goal from the dummy service, + // sorted newest-first by createdAt. + // TODO: Replace DummyStudentService with StudentService + // ***************************************************************** + private loadEvents() { + this.dummyService.getProgressEventsForGoal(this.goalId).then(result => { + if (!result.success) { + this.errorMessage.set(result.message); + } else { + const sorted = (result.payload ?? []) + .slice() + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + this.events.set(sorted); + } + }); + } + + // ***************************************************************** + // Loads the goal title from the student's goal list so the heading + // can display "Progress for ". + // ***************************************************************** + private loadGoalTitle() { + 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); + } + }); + } +} diff --git a/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts b/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts index 5ba5a31..2ca5e85 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { Home } from './pages/home/home'; import { StudentCardList } from './components/student-card-list/student-card-list'; import { GoalList } from './components/goal-list/goal-list'; +import { ProgressList } from './components/progress-list/progress-list'; export default [ { @@ -11,6 +12,7 @@ export default [ { path: '', redirectTo: 'students', pathMatch: 'full' }, { path: 'students', component: StudentCardList }, { path: 'students/:studentId/goals', component: GoalList }, + { path: 'students/:studentId/goals/:goalId/progress', component: ProgressList }, ], }, ] satisfies Routes; diff --git a/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.scss b/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.scss index 6c0d7cf..a47b048 100644 --- a/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.scss +++ b/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.scss @@ -69,7 +69,7 @@ border: 1px solid #ddd; border-radius: 8px; padding: 0.75rem; - font-size: 0.875rem; + font-size: 1rem; font-family: inherit; resize: vertical; margin-bottom: 1rem; diff --git a/ui/winstudentgoaltracker/src/app/shared/classes/progress-event.dto.ts b/ui/winstudentgoaltracker/src/app/shared/classes/progress-event.dto.ts new file mode 100644 index 0000000..7c965a8 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/shared/classes/progress-event.dto.ts @@ -0,0 +1,6 @@ +export interface ProgressEventDto { + progressEventId: string; + content: string; + createdAt: Date; + createdByName: string; +} diff --git a/ui/winstudentgoaltracker/src/app/shared/services/dummy-student.service.ts b/ui/winstudentgoaltracker/src/app/shared/services/dummy-student.service.ts index cf8c8c8..c997ef0 100644 --- a/ui/winstudentgoaltracker/src/app/shared/services/dummy-student.service.ts +++ b/ui/winstudentgoaltracker/src/app/shared/services/dummy-student.service.ts @@ -3,6 +3,7 @@ import { StudentCardDto } from '../classes/student-card.dto'; import { ApiResult } from '../classes/api-result'; import { StudentGoalSummary, StudentGoalItem } from '../classes/student-goal'; import { CreateGoalDto } from '../classes/create-goal.dto'; +import { ProgressEventDto } from '../classes/progress-event.dto'; @Injectable({ providedIn: 'root', @@ -19,39 +20,39 @@ export class DummyStudentService { 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 }, { 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 }, - { goalId: 'g3', goalParentId: null, title: 'Weekly journal entries', description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', progressEventCount: 8 }, + { goalId: 'g3', goalParentId: null, title: 'Weekly journal entries', description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', progressEventCount: 8 }, ], }, '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 }, - { goalId: 'g5', goalParentId: null, title: 'Attendance above 90%', description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', progressEventCount: 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 }, - { goalId: 'g7', goalParentId: null, title: 'Portfolio project', description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', progressEventCount: 1 }, + { 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 }, + { goalId: 'g5', goalParentId: null, title: 'Attendance above 90%', description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', progressEventCount: 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 }, + { goalId: 'g7', goalParentId: null, title: 'Portfolio project', description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', progressEventCount: 1 }, ], }, '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 }, - { goalId: 'g9', goalParentId: null, title: 'Resume workshop', description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', progressEventCount: 0 }, + { goalId: 'g8', goalParentId: null, title: 'GED preparation', description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', progressEventCount: 6 }, + { goalId: 'g9', goalParentId: null, title: 'Resume workshop', description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', progressEventCount: 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 }, - { 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 }, - { goalId: 'g12', goalParentId: null, title: 'Conflict resolution strategies', description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', progressEventCount: 2 }, - { goalId: 'g13', goalParentId: null, title: 'Daily attendance streak', description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', progressEventCount: 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 }, + { goalId: 'g10', goalParentId: null, title: 'Public speaking practice', description: 'Present in front of the class at least once per month.', category: 'Communication', progressEventCount: 4 }, + { 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 }, + { goalId: 'g12', goalParentId: null, title: 'Conflict resolution strategies', description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', progressEventCount: 2 }, + { goalId: 'g13', goalParentId: null, title: 'Daily attendance streak', description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', progressEventCount: 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 }, ], }, '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 }, + { goalId: 'g15', goalParentId: null, title: 'Improve typing speed', description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', progressEventCount: 3 }, ], }, }; @@ -65,7 +66,7 @@ export class DummyStudentService { // until the API endpoint is available. // ***************************************************************** async getMyStudents(): Promise> { - var payload = [ + var payload = [ { studentId: '1', identifier: 'J.B', @@ -97,8 +98,7 @@ export class DummyStudentService { async getGoalsForStudent(studentId: string): Promise> { var goals = this.data[studentId] ?? null; - if (goals === null) - { + if (goals === null) { return ApiResult.fail('Student not found'); } @@ -129,6 +129,38 @@ export class DummyStudentService { return ApiResult.empty(); } + // ***************************************************************** + // Returns hardcoded progress events for a given goal. The real + // service will call the API with the goal ID. + // TODO: Replace with actual API call + // ***************************************************************** + async getProgressEventsForGoal(goalId: string): Promise> { + const events: ProgressEventDto[] = [ + { progressEventId: 'pe1', content: 'Student demonstrated strong understanding of the topic during today\'s session. Completed all assigned exercises independently.', createdAt: new Date('2026-02-28T10:30:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe2', content: 'Reviewed previous week\'s material. Student needed some additional guidance but showed improvement by end of session.', createdAt: new Date('2026-02-27T14:15:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe3', content: 'Initial assessment completed. Identified key areas for focused practice going forward.', createdAt: new Date('2026-02-26T09:00:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe4', content: 'Practiced problem-solving strategies with real-world scenarios. Student engaged well and asked thoughtful questions.', createdAt: new Date('2026-02-25T11:00:00'), createdByName: 'Lisa Martinez' }, + { progressEventId: 'pe5', content: 'Worked on time management skills. Created a weekly planner and discussed prioritization techniques.', createdAt: new Date('2026-02-24T13:45:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe6', content: 'Student completed a timed practice exercise. Performance improved compared to last week.', createdAt: new Date('2026-02-21T10:00:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe7', content: 'Discussed long-term objectives and broke them into smaller milestones. Student is motivated and on track.', createdAt: new Date('2026-02-20T15:30:00'), createdByName: 'Lisa Martinez' }, + { progressEventId: 'pe8', content: 'Reviewed feedback from previous assignment. Student made corrections independently with minimal prompting.', createdAt: new Date('2026-02-19T09:30:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe9', content: 'Collaborative session with peer group. Student contributed actively and helped explain concepts to others.', createdAt: new Date('2026-02-18T14:00:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe10', content: 'Introduced new topic area. Student showed curiosity and took detailed notes for independent review.', createdAt: new Date('2026-02-14T11:15:00'), createdByName: 'Lisa Martinez' }, + { progressEventId: 'pe11', content: 'Student struggled with today\'s material but remained focused. Will revisit key concepts next session.', createdAt: new Date('2026-02-13T10:45:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe12', content: 'Follow-up on yesterday\'s challenging session. Student showed marked improvement after overnight reflection.', createdAt: new Date('2026-02-12T09:00:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe13', content: 'Mid-term progress check. Student is meeting expectations in most areas with room for growth in written expression.', createdAt: new Date('2026-02-11T13:00:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe14', content: 'Worked through a series of practice problems. Accuracy rate was 85%, up from 70% two weeks ago.', createdAt: new Date('2026-02-10T10:30:00'), createdByName: 'Lisa Martinez' }, + { progressEventId: 'pe15', content: 'Student presented a short summary of recent learning to the group. Showed confidence and clarity.', createdAt: new Date('2026-02-07T14:30:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe16', content: 'Explored supplementary resources together. Student identified two additional practice tools to use independently.', createdAt: new Date('2026-02-06T11:00:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe17', content: 'Reviewed study habits and discussed strategies for staying consistent. Student committed to a daily review routine.', createdAt: new Date('2026-02-05T09:15:00'), createdByName: 'Lisa Martinez' }, + { progressEventId: 'pe18', content: 'Hands-on activity session. Student completed the project ahead of schedule with strong attention to detail.', createdAt: new Date('2026-02-04T13:30:00'), createdByName: 'Sarah Johnson' }, + { progressEventId: 'pe19', content: 'Addressed gaps identified in the initial assessment. Student showed solid understanding of foundational concepts.', createdAt: new Date('2026-02-03T10:00:00'), createdByName: 'Mike Thompson' }, + { progressEventId: 'pe20', content: 'First session of the term. Established rapport and set expectations for the upcoming weeks.', createdAt: new Date('2026-01-31T09:00:00'), createdByName: 'Sarah Johnson' }, + ]; + + return ApiResult.ok(events); + } + // ************************ Event Handlers *************************