From 53d0539d28299451a361f2e0c4d87bd92e039608 Mon Sep 17 00:00:00 2001 From: ivan-pelly Date: Sun, 15 Mar 2026 09:35:58 -0700 Subject: [PATCH] Added Goals fields --- api/src/Controllers/StudentController.cs | 70 +++++- .../AddProgressEventDto.cs | 1 + .../DataTransferObjects/CreateGoalDto.cs | 1 + .../DataTransferObjects/UpdateGoalDto.cs | 4 + .../UpdateProgressEventDto.cs | 7 + .../DatabaseObjects/dbStudentGoalRow.cs | 4 + .../Repositories/StudentRepository.cs | 58 ++++- .../Models/ResponseTypes/StudentGoalItem.cs | 4 + .../Models/ResponseTypes/StudentResponse.cs | 1 + db/Objects/procedures/sp_Goal_Delete.sql | 8 - db/Objects/procedures/sp_Goal_GetAll.sql | 15 -- db/Objects/procedures/sp_Goal_GetById.sql | 4 + .../procedures/sp_Goal_GetByStudentId.sql | 6 +- db/Objects/procedures/sp_Goal_Insert.sql | 6 +- db/Objects/procedures/sp_Goal_Update.sql | 10 +- ...sp_ProgressEventBenchmark_GetByEventId.sql | 11 + .../procedures/sp_ProgressEvent_Delete.sql | 8 - .../procedures/sp_ProgressEvent_GetAll.sql | 16 -- .../procedures/sp_ProgressEvent_GetById.sql | 17 -- .../procedures/sp_ProgressEvent_Insert.sql | 42 ---- .../procedures/sp_ProgressEvent_Save.sql | 62 +++++ .../procedures/sp_ProgressEvent_Update.sql | 22 -- db/Objects/procedures/sp_Student_GetAll.sql | 14 -- db/Objects/procedures/sp_Student_GetById.sql | 3 +- .../sp_Student_GetWithAssignments.sql | 5 +- db/Objects/tables/goal.sql | 4 + db/Objects/views/v_goal_card.sql | 4 +- .../add-goal-modal/add-goal-modal.html | 12 + .../add-goal-modal/add-goal-modal.ts | 12 + .../benchmark-card-full.html | 8 +- .../goal-card-full/goal-card-full.html | 96 +++++--- .../goal-card-full/goal-card-full.scss | 81 ++++++- .../goal-card-full/goal-card-full.ts | 46 +++- .../components/goal-card/goal-card.html | 22 +- .../components/goal-card/goal-card.scss | 107 +++++---- .../desktop/components/goal-card/goal-card.ts | 3 +- .../components/goal-list/goal-list.html | 2 +- .../components/goal-list/goal-list.scss | 3 +- .../desktop/components/goal-list/goal-list.ts | 8 + .../progress-edit/progress-edit.html | 58 ++++- .../progress-edit/progress-edit.scss | 163 +++++++++++++ .../components/progress-edit/progress-edit.ts | 223 +++++++++++++++++- .../progress-item/progress-item.html | 2 +- .../progress-item/progress-item.scss | 22 +- .../components/progress-item/progress-item.ts | 9 +- .../components/progress-list/progress-list.ts | 3 +- .../sidebar-tree-node/sidebar-tree-node.html | 2 + .../sidebar-tree-node/sidebar-tree-node.scss | 5 + .../student-card-full/student-card-full.html | 32 ++- .../student-card-full/student-card-full.scss | 43 +++- .../components/student-card/student-card.html | 1 + .../src/app/desktop/desktop.routes.ts | 3 + .../toggle-benchmark/toggle-benchmark.html | 4 + .../toggle-benchmark/toggle-benchmark.scss | 46 ++++ .../toggle-benchmark/toggle-benchmark.spec.ts | 23 ++ .../toggle-benchmark/toggle-benchmark.ts | 22 ++ .../add-progress-event.html | 15 +- .../add-progress-event.scss | 11 + .../add-progress-event/add-progress-event.ts | 50 +++- .../src/app/shared/classes/create-goal.dto.ts | 1 + .../app/shared/classes/student-card.dto.ts | 1 + .../src/app/shared/classes/student-goal.ts | 4 + .../src/app/shared/services/auth.ts | 10 +- .../shared/services/dummy-student.service.ts | 37 +-- .../app/shared/services/student.service.ts | 50 +++- ui/winstudentgoaltracker/src/styles.scss | 4 + 66 files changed, 1322 insertions(+), 329 deletions(-) create mode 100644 api/src/DataAccess/Models/DataTransferObjects/UpdateProgressEventDto.cs delete mode 100644 db/Objects/procedures/sp_Goal_Delete.sql delete mode 100644 db/Objects/procedures/sp_Goal_GetAll.sql create mode 100644 db/Objects/procedures/sp_ProgressEventBenchmark_GetByEventId.sql delete mode 100644 db/Objects/procedures/sp_ProgressEvent_Delete.sql delete mode 100644 db/Objects/procedures/sp_ProgressEvent_GetAll.sql delete mode 100644 db/Objects/procedures/sp_ProgressEvent_GetById.sql delete mode 100644 db/Objects/procedures/sp_ProgressEvent_Insert.sql create mode 100644 db/Objects/procedures/sp_ProgressEvent_Save.sql delete mode 100644 db/Objects/procedures/sp_ProgressEvent_Update.sql delete mode 100644 db/Objects/procedures/sp_Student_GetAll.sql create mode 100644 ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.html create mode 100644 ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.scss create mode 100644 ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.spec.ts create mode 100644 ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.ts diff --git a/api/src/Controllers/StudentController.cs b/api/src/Controllers/StudentController.cs index 2964d37..1a9680e 100644 --- a/api/src/Controllers/StudentController.cs +++ b/api/src/Controllers/StudentController.cs @@ -294,8 +294,10 @@ public class StudentController : BaseController }); } - var created = await _studentRepository.AddProgressEventAsync(userId, dto); - if (!created) + var newId = Guid.NewGuid(); + var created = await _studentRepository.SaveProgressEventAsync( + newId, dto.GoalId, userId, dto.Content, isNew: true, dto.BenchmarkIds); + if (created is null) { return BadRequest(new ResponseResult { @@ -304,10 +306,70 @@ public class StudentController : BaseController }); } - return StatusCode(StatusCodes.Status201Created, new ResponseResult + return StatusCode(StatusCodes.Status201Created, new ResponseResult { Success = true, - Message = "Progress event added successfully." + Message = "Progress event added successfully.", + Data = new { progressEventId = created.Value } + }); + } + + [HttpPut("{idStudent:guid}/progress-events/{idProgressEvent:guid}")] + [Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")] + [ProducesResponseType(typeof(ResponseResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ResponseResult), StatusCodes.Status404NotFound)] + public async Task> UpdateProgressEvent( + Guid idStudent, Guid idProgressEvent, [FromBody] UpdateProgressEventDto dto) + { + var (userId, email, programId, role, error) = GetProgramUserFromClaims(); + if (error is not null) + { + return error; + } + + var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role); + if (!students.Select(s => s.StudentId).Contains(idStudent)) + { + return NotFound(new ResponseResult + { + Success = false, + Message = "Student not found." + }); + } + + try + { + await _studentRepository.SaveProgressEventAsync( + idProgressEvent, Guid.Empty, userId, dto.Content, isNew: false, dto.BenchmarkIds); + + return Ok(new ResponseResult + { + Success = true, + Message = "Progress event updated successfully." + }); + } + catch (Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, new ResponseResult + { + Success = false, + Message = $"[DIAG] {ex.GetType().Name}: {ex.Message} | Inner: {ex.InnerException?.Message}" + }); + } + } + + [HttpGet("progress-events/{idProgressEvent:guid}/benchmarks")] + [Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")] + [ProducesResponseType(typeof(ResponseResult>), StatusCodes.Status200OK)] + public async Task>>> GetProgressEventBenchmarks(Guid idProgressEvent) + { + var benchmarkIds = await _studentRepository.GetBenchmarkIdsForEventAsync(idProgressEvent); + + return Ok(new ResponseResult> + { + Success = true, + Message = "Benchmark associations retrieved.", + Data = benchmarkIds }); } diff --git a/api/src/DataAccess/Models/DataTransferObjects/AddProgressEventDto.cs b/api/src/DataAccess/Models/DataTransferObjects/AddProgressEventDto.cs index 911e3cd..6ccdacd 100644 --- a/api/src/DataAccess/Models/DataTransferObjects/AddProgressEventDto.cs +++ b/api/src/DataAccess/Models/DataTransferObjects/AddProgressEventDto.cs @@ -5,4 +5,5 @@ public class AddProgressEventDto public Guid GoalId { get; set; } public string? Content { get; set; } public bool IsSensitive { get; set; } + public List? BenchmarkIds { get; set; } } diff --git a/api/src/DataAccess/Models/DataTransferObjects/CreateGoalDto.cs b/api/src/DataAccess/Models/DataTransferObjects/CreateGoalDto.cs index 4ba2d39..21881ff 100644 --- a/api/src/DataAccess/Models/DataTransferObjects/CreateGoalDto.cs +++ b/api/src/DataAccess/Models/DataTransferObjects/CreateGoalDto.cs @@ -6,4 +6,5 @@ public class CreateGoalDto public string? Category { get; set; } public string? Baseline { get; set; } public Guid? GoalParentId { get; set; } + public DateTime? TargetCompletionDate { get; set; } } diff --git a/api/src/DataAccess/Models/DataTransferObjects/UpdateGoalDto.cs b/api/src/DataAccess/Models/DataTransferObjects/UpdateGoalDto.cs index 9c76f7c..bca820e 100644 --- a/api/src/DataAccess/Models/DataTransferObjects/UpdateGoalDto.cs +++ b/api/src/DataAccess/Models/DataTransferObjects/UpdateGoalDto.cs @@ -5,4 +5,8 @@ public class UpdateGoalDto public string? Description { get; set; } public string? Category { get; set; } public string? Baseline { get; set; } + public DateTime? TargetCompletionDate { get; set; } + public DateTime? CloseDate { get; set; } + public bool? Achieved { get; set; } + public string? CloseNotes { get; set; } } diff --git a/api/src/DataAccess/Models/DataTransferObjects/UpdateProgressEventDto.cs b/api/src/DataAccess/Models/DataTransferObjects/UpdateProgressEventDto.cs new file mode 100644 index 0000000..fa28ce0 --- /dev/null +++ b/api/src/DataAccess/Models/DataTransferObjects/UpdateProgressEventDto.cs @@ -0,0 +1,7 @@ +namespace WinStudentGoalTracker.DataAccess; + +public class UpdateProgressEventDto +{ + public string? Content { get; set; } + public List? BenchmarkIds { get; set; } +} diff --git a/api/src/DataAccess/Models/DatabaseObjects/dbStudentGoalRow.cs b/api/src/DataAccess/Models/DatabaseObjects/dbStudentGoalRow.cs index cb8cfd5..c83b7f4 100644 --- a/api/src/DataAccess/Models/DatabaseObjects/dbStudentGoalRow.cs +++ b/api/src/DataAccess/Models/DatabaseObjects/dbStudentGoalRow.cs @@ -8,6 +8,10 @@ public class dbStudentGoalRow public string? Description { get; set; } public string? Category { get; set; } public string? Baseline { get; set; } + public DateTime? TargetCompletionDate { get; set; } + public DateTime? CloseDate { get; set; } + public bool? Achieved { get; set; } + public string? CloseNotes { get; set; } public int ProgressEventCount { get; set; } public int BenchmarkCount { get; set; } } diff --git a/api/src/DataAccess/Repositories/StudentRepository.cs b/api/src/DataAccess/Repositories/StudentRepository.cs index 6fea84d..a88e7f7 100644 --- a/api/src/DataAccess/Repositories/StudentRepository.cs +++ b/api/src/DataAccess/Repositories/StudentRepository.cs @@ -84,21 +84,49 @@ public class StudentRepository return rowsAffected > 0; } - public async Task AddProgressEventAsync(Guid userId, AddProgressEventDto dto) + // ***************************************************************** + // Saves a progress event (insert or update) and syncs benchmark + // associations in a single stored procedure call. + // ***************************************************************** + public async Task SaveProgressEventAsync( + Guid progressEventId, Guid goalId, Guid userId, + string? content, bool isNew, List? benchmarkIds) { + var idsCsv = benchmarkIds is { Count: > 0 } + ? string.Join(",", benchmarkIds.Select(id => id.ToString())) + : null; + using var db = Connection; - var row = await db.QuerySingleOrDefaultAsync( - "sp_ProgressEvent_Insert", + var row = await db.QuerySingleOrDefaultAsync( + "sp_ProgressEvent_Save", new { - p_id_progress_event = Guid.NewGuid().ToString(), - p_id_goal = dto.GoalId.ToString(), + p_id_progress_event = progressEventId.ToString(), + p_id_goal = goalId.ToString(), p_id_user_created = userId.ToString(), - p_content = dto.Content, - p_is_sensitive = dto.IsSensitive ? 1 : 0 + p_content = content, + p_is_sensitive = 0, + p_is_new = isNew ? 1 : 0, + p_benchmark_ids = idsCsv }, commandType: CommandType.StoredProcedure); - return row is not null; + + if (row is null) return null; + return row.progressEventId is Guid g ? g : Guid.Parse((string)row.progressEventId); + } + + // ***************************************************************** + // Returns the benchmark IDs associated with a progress event. + // ***************************************************************** + public async Task> GetBenchmarkIdsForEventAsync(Guid progressEventId) + { + using var db = Connection; + var rows = await db.QueryAsync( + "sp_ProgressEventBenchmark_GetByEventId", + new { p_id_progress_event = progressEventId.ToString() }, + commandType: CommandType.StoredProcedure); + + return rows.Select(r => r.benchmarkId is Guid g ? g : Guid.Parse((string)r.benchmarkId)).ToList(); } public async Task GetStudentIdForGoalAsync(Guid idGoal) @@ -146,7 +174,8 @@ public class StudentRepository p_id_user_created = userId.ToString(), p_description = dto.Description, p_category = dto.Category, - p_baseline = dto.Baseline + p_baseline = dto.Baseline, + p_target_completion_date = dto.TargetCompletionDate }, commandType: CommandType.StoredProcedure); @@ -159,6 +188,7 @@ public class StudentRepository Description = dto.Description, Category = dto.Category, Baseline = dto.Baseline, + TargetCompletionDate = dto.TargetCompletionDate, ProgressEventCount = 0 }; } @@ -194,6 +224,10 @@ public class StudentRepository Description = r.Description, Category = r.Category, Baseline = r.Baseline, + TargetCompletionDate = r.TargetCompletionDate, + CloseDate = r.CloseDate, + Achieved = r.Achieved, + CloseNotes = r.CloseNotes, ProgressEventCount = r.ProgressEventCount, BenchmarkCount = r.BenchmarkCount }).ToList() @@ -216,7 +250,11 @@ public class StudentRepository p_id_user_created = (string?)null, p_description = dto.Description, p_category = dto.Category, - p_baseline = dto.Baseline + p_baseline = dto.Baseline, + p_target_completion_date = dto.TargetCompletionDate, + p_close_date = dto.CloseDate, + p_achieved = dto.Achieved, + p_close_notes = dto.CloseNotes }, commandType: CommandType.StoredProcedure); return rowsAffected > 0; diff --git a/api/src/Models/ResponseTypes/StudentGoalItem.cs b/api/src/Models/ResponseTypes/StudentGoalItem.cs index 26acab4..dcba2ef 100644 --- a/api/src/Models/ResponseTypes/StudentGoalItem.cs +++ b/api/src/Models/ResponseTypes/StudentGoalItem.cs @@ -7,6 +7,10 @@ public class StudentGoalItem public string? Description { get; set; } public string? Category { get; set; } public string? Baseline { get; set; } + public DateTime? TargetCompletionDate { get; set; } + public DateTime? CloseDate { get; set; } + public bool? Achieved { get; set; } + public string? CloseNotes { get; set; } public int ProgressEventCount { get; set; } public int BenchmarkCount { get; set; } } diff --git a/api/src/Models/ResponseTypes/StudentResponse.cs b/api/src/Models/ResponseTypes/StudentResponse.cs index 47c3822..bfa5508 100644 --- a/api/src/Models/ResponseTypes/StudentResponse.cs +++ b/api/src/Models/ResponseTypes/StudentResponse.cs @@ -8,4 +8,5 @@ public class StudentResponse public DateTime? LastEntryDate { get; set; } public int GoalCount { get; set; } public int ProgressEventCount { get; set; } + public int BenchmarkCount { get; set; } } diff --git a/db/Objects/procedures/sp_Goal_Delete.sql b/db/Objects/procedures/sp_Goal_Delete.sql deleted file mode 100644 index 9af9989..0000000 --- a/db/Objects/procedures/sp_Goal_Delete.sql +++ /dev/null @@ -1,8 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_Delete`(IN p_id_goal CHAR(36)) -BEGIN - DELETE FROM goal - WHERE id_goal = p_id_goal; - SELECT ROW_COUNT() AS rows_affected; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_Goal_GetAll.sql b/db/Objects/procedures/sp_Goal_GetAll.sql deleted file mode 100644 index 04f2ba3..0000000 --- a/db/Objects/procedures/sp_Goal_GetAll.sql +++ /dev/null @@ -1,15 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_GetAll`() -BEGIN - SELECT - goalId, - goalParentId, - studentId, - description, - category, - baseline, - progressEventCount - FROM v_goal_card - ORDER BY goalId; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_Goal_GetById.sql b/db/Objects/procedures/sp_Goal_GetById.sql index 362c2ff..e34a4c8 100644 --- a/db/Objects/procedures/sp_Goal_GetById.sql +++ b/db/Objects/procedures/sp_Goal_GetById.sql @@ -8,6 +8,10 @@ BEGIN description, category, baseline, + targetCompletionDate, + closeDate, + achieved, + closeNotes, progressEventCount FROM v_goal_card WHERE goalId = p_id_goal diff --git a/db/Objects/procedures/sp_Goal_GetByStudentId.sql b/db/Objects/procedures/sp_Goal_GetByStudentId.sql index 3428425..dbd937c 100644 --- a/db/Objects/procedures/sp_Goal_GetByStudentId.sql +++ b/db/Objects/procedures/sp_Goal_GetByStudentId.sql @@ -2,12 +2,16 @@ DELIMITER ;; CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_GetByStudentId`(IN p_id_student CHAR(36)) BEGIN SELECT - s.`identifier` AS `studentIdentifier`, + s.`identifier` AS `studentIdentifier`, vc.`goalId`, vc.`goalParentId`, vc.`description`, vc.`category`, vc.`baseline`, + vc.`targetCompletionDate`, + vc.`closeDate`, + vc.`achieved`, + vc.`closeNotes`, vc.`progressEventCount`, vc.`benchmarkCount` FROM `v_goal_card` vc diff --git a/db/Objects/procedures/sp_Goal_Insert.sql b/db/Objects/procedures/sp_Goal_Insert.sql index 092c1c4..70338df 100644 --- a/db/Objects/procedures/sp_Goal_Insert.sql +++ b/db/Objects/procedures/sp_Goal_Insert.sql @@ -6,7 +6,8 @@ CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_Insert`( IN p_id_user_created CHAR(36), IN p_description TEXT, IN p_category VARCHAR(255), - IN p_baseline TEXT + IN p_baseline TEXT, + IN p_target_completion_date DATE ) BEGIN INSERT INTO goal @@ -18,6 +19,7 @@ BEGIN description, category, baseline, + target_completion_date, created_at, updated_at ) @@ -30,6 +32,7 @@ BEGIN p_description, p_category, p_baseline, + p_target_completion_date, UTC_TIMESTAMP(), UTC_TIMESTAMP() ); @@ -41,6 +44,7 @@ BEGIN description, category, baseline, + target_completion_date, created_at, updated_at FROM goal diff --git a/db/Objects/procedures/sp_Goal_Update.sql b/db/Objects/procedures/sp_Goal_Update.sql index 7825217..e0efa48 100644 --- a/db/Objects/procedures/sp_Goal_Update.sql +++ b/db/Objects/procedures/sp_Goal_Update.sql @@ -6,7 +6,11 @@ CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_Update`( IN p_id_user_created CHAR(36), IN p_description TEXT, IN p_category VARCHAR(255), - IN p_baseline TEXT + IN p_baseline TEXT, + IN p_target_completion_date DATE, + IN p_close_date DATE, + IN p_achieved TINYINT(1), + IN p_close_notes TEXT ) BEGIN UPDATE goal @@ -17,6 +21,10 @@ BEGIN description = COALESCE(p_description, description), category = COALESCE(p_category, category), baseline = COALESCE(p_baseline, baseline), + target_completion_date = COALESCE(p_target_completion_date, target_completion_date), + close_date = COALESCE(p_close_date, close_date), + achieved = COALESCE(p_achieved, achieved), + close_notes = COALESCE(p_close_notes, close_notes), updated_at = UTC_TIMESTAMP() WHERE id_goal = p_id_goal; SELECT ROW_COUNT() AS rows_affected; diff --git a/db/Objects/procedures/sp_ProgressEventBenchmark_GetByEventId.sql b/db/Objects/procedures/sp_ProgressEventBenchmark_GetByEventId.sql new file mode 100644 index 0000000..4952c48 --- /dev/null +++ b/db/Objects/procedures/sp_ProgressEventBenchmark_GetByEventId.sql @@ -0,0 +1,11 @@ +DELIMITER ;; +CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEventBenchmark_GetByEventId`( + IN p_id_progress_event CHAR(36) +) +BEGIN + SELECT + peb.id_benchmark AS benchmarkId + FROM progress_event_benchmark peb + WHERE peb.id_progress_event = p_id_progress_event; +END;; +DELIMITER ; diff --git a/db/Objects/procedures/sp_ProgressEvent_Delete.sql b/db/Objects/procedures/sp_ProgressEvent_Delete.sql deleted file mode 100644 index 7cb0672..0000000 --- a/db/Objects/procedures/sp_ProgressEvent_Delete.sql +++ /dev/null @@ -1,8 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEvent_Delete`(IN p_id_progress_event CHAR(36)) -BEGIN - DELETE FROM progress_event - WHERE id_progress_event = p_id_progress_event; - SELECT ROW_COUNT() AS rows_affected; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_ProgressEvent_GetAll.sql b/db/Objects/procedures/sp_ProgressEvent_GetAll.sql deleted file mode 100644 index 3fa3152..0000000 --- a/db/Objects/procedures/sp_ProgressEvent_GetAll.sql +++ /dev/null @@ -1,16 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEvent_GetAll`() -BEGIN - SELECT - id_progress_event, - id_student, - id_goal, - id_user_created, - content, - is_sensitive, - created_at, - updated_at - FROM progress_event - ORDER BY id_progress_event; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_ProgressEvent_GetById.sql b/db/Objects/procedures/sp_ProgressEvent_GetById.sql deleted file mode 100644 index 5c261f6..0000000 --- a/db/Objects/procedures/sp_ProgressEvent_GetById.sql +++ /dev/null @@ -1,17 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEvent_GetById`(IN p_id_progress_event CHAR(36)) -BEGIN - SELECT - id_progress_event, - id_student, - id_goal, - id_user_created, - content, - is_sensitive, - created_at, - updated_at - FROM progress_event - WHERE id_progress_event = p_id_progress_event - LIMIT 1; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_ProgressEvent_Insert.sql b/db/Objects/procedures/sp_ProgressEvent_Insert.sql deleted file mode 100644 index b8c2235..0000000 --- a/db/Objects/procedures/sp_ProgressEvent_Insert.sql +++ /dev/null @@ -1,42 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEvent_Insert`( - IN p_id_progress_event CHAR(36), - IN p_id_goal CHAR(36), - IN p_id_user_created CHAR(36), - IN p_content TEXT, - IN p_is_sensitive TINYINT(1) -) -BEGIN - INSERT INTO progress_event - ( - id_progress_event, - id_goal, - id_user_created, - content, - is_sensitive, - created_at, - updated_at - ) - VALUES - ( - p_id_progress_event, - p_id_goal, - p_id_user_created, - p_content, - p_is_sensitive, - UTC_TIMESTAMP(), - UTC_TIMESTAMP() - ); - SELECT - id_progress_event, - id_goal, - id_user_created, - content, - is_sensitive, - created_at, - updated_at - FROM progress_event - WHERE id_progress_event = p_id_progress_event - LIMIT 1; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_ProgressEvent_Save.sql b/db/Objects/procedures/sp_ProgressEvent_Save.sql new file mode 100644 index 0000000..915d265 --- /dev/null +++ b/db/Objects/procedures/sp_ProgressEvent_Save.sql @@ -0,0 +1,62 @@ +DELIMITER ;; +CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEvent_Save`( + IN p_id_progress_event CHAR(36), + IN p_id_goal CHAR(36), + IN p_id_user_created CHAR(36), + IN p_content TEXT, + IN p_is_sensitive TINYINT(1), + IN p_is_new TINYINT(1), + IN p_benchmark_ids TEXT +) +BEGIN + -- Insert or update the progress event + IF p_is_new = 1 THEN + INSERT INTO progress_event + ( + id_progress_event, + id_goal, + id_user_created, + content, + is_sensitive, + created_at, + updated_at + ) + VALUES + ( + p_id_progress_event, + p_id_goal, + p_id_user_created, + p_content, + p_is_sensitive, + UTC_TIMESTAMP(), + UTC_TIMESTAMP() + ); + ELSE + UPDATE progress_event + SET + content = COALESCE(p_content, content), + updated_at = UTC_TIMESTAMP() + WHERE id_progress_event = p_id_progress_event; + END IF; + -- Sync benchmark associations: remove those not in the list + DELETE FROM progress_event_benchmark + WHERE id_progress_event = p_id_progress_event + AND (p_benchmark_ids IS NULL + OR LENGTH(TRIM(p_benchmark_ids)) = 0 + OR FIND_IN_SET(id_benchmark, p_benchmark_ids) = 0); + -- Add associations that are in the list but not yet in the table + IF p_benchmark_ids IS NOT NULL AND LENGTH(TRIM(p_benchmark_ids)) > 0 THEN + INSERT INTO progress_event_benchmark (id_progress_event_benchmark, id_progress_event, id_benchmark, created_at) + SELECT UUID(), p_id_progress_event, b.id_benchmark, UTC_TIMESTAMP() + FROM benchmark b + WHERE FIND_IN_SET(b.id_benchmark, p_benchmark_ids) > 0 + AND b.id_benchmark NOT IN ( + SELECT peb.id_benchmark + FROM progress_event_benchmark peb + WHERE peb.id_progress_event = p_id_progress_event + ); + END IF; + -- Return the progress event ID for the caller + SELECT p_id_progress_event AS progressEventId; +END;; +DELIMITER ; diff --git a/db/Objects/procedures/sp_ProgressEvent_Update.sql b/db/Objects/procedures/sp_ProgressEvent_Update.sql deleted file mode 100644 index a9c8d18..0000000 --- a/db/Objects/procedures/sp_ProgressEvent_Update.sql +++ /dev/null @@ -1,22 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_ProgressEvent_Update`( - IN p_id_progress_event CHAR(36), - IN p_id_student CHAR(36), - IN p_id_goal CHAR(36), - IN p_id_user_created CHAR(36), - IN p_content TEXT, - IN p_is_sensitive TINYINT(1) -) -BEGIN - UPDATE progress_event - SET - id_student = COALESCE(p_id_student, id_student), - id_goal = COALESCE(p_id_goal, id_goal), - id_user_created = COALESCE(p_id_user_created, id_user_created), - content = COALESCE(p_content, content), - is_sensitive = COALESCE(p_is_sensitive, is_sensitive), - updated_at = UTC_TIMESTAMP() - WHERE id_progress_event = p_id_progress_event; - SELECT ROW_COUNT() AS rows_affected; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_Student_GetAll.sql b/db/Objects/procedures/sp_Student_GetAll.sql deleted file mode 100644 index 59269f8..0000000 --- a/db/Objects/procedures/sp_Student_GetAll.sql +++ /dev/null @@ -1,14 +0,0 @@ -DELIMITER ;; -CREATE DEFINER=`root`@`%` PROCEDURE `sp_Student_GetAll`() -BEGIN - SELECT - studentId, - identifier, - nextIepDate, - lastEntryDate, - goalCount, - progressEventCount - FROM v_student_card - ORDER BY studentId; -END;; -DELIMITER ; diff --git a/db/Objects/procedures/sp_Student_GetById.sql b/db/Objects/procedures/sp_Student_GetById.sql index 9e75526..30218ad 100644 --- a/db/Objects/procedures/sp_Student_GetById.sql +++ b/db/Objects/procedures/sp_Student_GetById.sql @@ -7,7 +7,8 @@ BEGIN nextIepDate, lastEntryDate, goalCount, - progressEventCount + progressEventCount, + benchmarkCount FROM v_student_card WHERE studentId = p_id_student LIMIT 1; diff --git a/db/Objects/procedures/sp_Student_GetWithAssignments.sql b/db/Objects/procedures/sp_Student_GetWithAssignments.sql index 6a942ea..f65adbc 100644 --- a/db/Objects/procedures/sp_Student_GetWithAssignments.sql +++ b/db/Objects/procedures/sp_Student_GetWithAssignments.sql @@ -4,19 +4,18 @@ CREATE DEFINER=`root`@`%` PROCEDURE `sp_Student_GetWithAssignments`( IN p_id_user CHAR(36) ) BEGIN - -- Result set 1: All students in the program (card shape) SELECT vc.studentId, vc.identifier, vc.nextIepDate, vc.lastEntryDate, vc.goalCount, - vc.progressEventCount + vc.progressEventCount, + vc.benchmarkCount FROM v_student_card vc INNER JOIN student s ON s.id_student = vc.studentId WHERE s.id_program = p_id_program ORDER BY vc.studentId; - -- Result set 2: user_student assignments for the requesting user in this program SELECT us.id_user_student, us.id_user, diff --git a/db/Objects/tables/goal.sql b/db/Objects/tables/goal.sql index 9f8c3e5..d489971 100644 --- a/db/Objects/tables/goal.sql +++ b/db/Objects/tables/goal.sql @@ -6,6 +6,10 @@ CREATE TABLE `goal` ( `description` text, `category` varchar(255) DEFAULT NULL, `baseline` text, + `target_completion_date` date DEFAULT NULL, + `close_date` date DEFAULT NULL, + `achieved` tinyint(1) DEFAULT NULL, + `close_notes` text, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id_goal`), diff --git a/db/Objects/views/v_goal_card.sql b/db/Objects/views/v_goal_card.sql index 7835cac..e5fb829 100644 --- a/db/Objects/views/v_goal_card.sql +++ b/db/Objects/views/v_goal_card.sql @@ -1,7 +1,7 @@ CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `winstudentgoaltracker`.`v_goal_card` AS -select `g`.`id_goal` AS `goalId`,`g`.`id_goal_parent` AS `goalParentId`,`g`.`id_student` AS `studentId`,`g`.`description` AS `description`,`g`.`category` AS `category`,`g`.`baseline` AS `baseline`,count(distinct `pe`.`id_progress_event`) AS `progressEventCount`,count(distinct `b`.`id_benchmark`) AS `benchmarkCount` +select `g`.`id_goal` AS `goalId`,`g`.`id_goal_parent` AS `goalParentId`,`g`.`id_student` AS `studentId`,`g`.`description` AS `description`,`g`.`category` AS `category`,`g`.`baseline` AS `baseline`,`g`.`target_completion_date` AS `targetCompletionDate`,`g`.`close_date` AS `closeDate`,`g`.`achieved` AS `achieved`,`g`.`close_notes` AS `closeNotes`,count(distinct `pe`.`id_progress_event`) AS `progressEventCount`,count(distinct `b`.`id_benchmark`) AS `benchmarkCount` from ((`winstudentgoaltracker`.`goal` `g` left join `winstudentgoaltracker`.`progress_event` `pe` on((`pe`.`id_goal` = `g`.`id_goal`))) left -join `winstudentgoaltracker`.`benchmark` `b` on((`b`.`id_goal` = `g`.`id_goal`))) group by `g`.`id_goal`,`g`.`id_goal_parent`,`g`.`id_student`,`g`.`description`,`g`.`category`,`g`.`baseline`; +join `winstudentgoaltracker`.`benchmark` `b` on((`b`.`id_goal` = `g`.`id_goal`))) group by `g`.`id_goal`,`g`.`id_goal_parent`,`g`.`id_student`,`g`.`description`,`g`.`category`,`g`.`baseline`,`g`.`target_completion_date`,`g`.`close_date`,`g`.`achieved`,`g`.`close_notes`; diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.html b/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.html index a071034..407d9a6 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.html @@ -42,6 +42,17 @@ > +
+ + +
+ + @if (errorMessage()) {

{{ errorMessage() }}

diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.ts b/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.ts index 36e7746..61feb4d 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/add-goal-modal/add-goal-modal.ts @@ -20,6 +20,7 @@ export class AddGoalModal { readonly studentId = input.required(); readonly existingGoals = input.required(); + readonly nextIepDate = input(); readonly goalCreated = output(); readonly cancelled = output(); @@ -35,8 +36,19 @@ export class AddGoalModal { category: '', baseline: '', goalParentId: null, + targetCompletionDate: null, }; + // ***************************************************************** + // Pre-fills targetCompletionDate from the student's nextIepDate. + // ***************************************************************** + ngOnInit() { + const iepDate = this.nextIepDate?.(); + if (iepDate) { + this.form.targetCompletionDate = iepDate; + } + } + // ************************** Properties *************************** // ************************ Public Methods ************************* diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/benchmark-card-full/benchmark-card-full.html b/ui/winstudentgoaltracker/src/app/desktop/components/benchmark-card-full/benchmark-card-full.html index 27956ec..26221dc 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/benchmark-card-full/benchmark-card-full.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/benchmark-card-full/benchmark-card-full.html @@ -8,10 +8,6 @@

{{ errorMessage() }}

} -@if (successMessage()) { -

{{ successMessage() }}

-} - @if (loaded()) {
@@ -45,4 +41,8 @@
+} + +@if (successMessage()) { +

{{ successMessage() }}

} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.html b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.html index 91b6622..8b4bd1d 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.html @@ -4,40 +4,76 @@ +@if (loaded()) { +
+
+ Goal: {{ category }} + @if (targetCompletionDate) { + Target: {{ targetCompletionDate | date:'mediumDate' }} + } @else { + No target date + } +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + @if (closeDate !== null) { +
+
+ + +
+
+ + +
+ @if (achieved === false) { +
+ + +
+ } +
+ } + +
+ + +
+
+ + +
+} + @if (errorMessage()) {

{{ errorMessage() }}

} @if (successMessage()) {

{{ successMessage() }}

-} - -@if (loaded()) { -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
} \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.scss b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.scss index 2ab516b..5bb3225 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.scss +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.scss @@ -58,40 +58,77 @@ .error { font-size: 0.875rem; color: #dc2626; - margin: 0 0 1rem; + margin: 1rem 0 0; } .success { font-size: 0.875rem; color: #16a34a; - margin: 0 0 1rem; + margin: 1rem 0 0; } .detail-card { background: #fff; border: 1px solid #ddd; border-radius: 8px; - padding: 1.5rem; max-width: 600px; + overflow: hidden; +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.625rem 1.25rem; + background: #f8f9fa; + border-bottom: 1px solid #ddd; +} + +.card-title { + font-size: 0.875rem; + font-weight: 600; + color: #333; +} + +.card-body { + padding: 1.5rem; } .card-footer { display: flex; + align-items: center; gap: 1.5rem; - margin-top: 1rem; - padding-top: 1rem; + padding: 0.5rem 1.5rem; border-top: 1px solid #eee; } -.detail-link { - font-size: 0.875rem; - color: #4f46e5; +.footer-btn { + padding: 0.375rem 0.75rem; + background: transparent; + border: 1px solid; + border-radius: 6px; + font-size: 0.8125rem; + font-weight: 500; cursor: pointer; - text-decoration: underline; } -.detail-link:hover { - color: #4338ca; +.btn-green { + color: #16a34a; + border-color: #16a34a; +} + +.btn-green:hover { + background: #f0fdf4; +} + +.btn-blue { + color: #2563eb; + border-color: #2563eb; + margin-left: auto; +} + +.btn-blue:hover { + background: #eff6ff; } .field { @@ -113,6 +150,7 @@ padding: 0.375rem 0.5rem; border: 1px solid #ccc; border-radius: 6px; + font-family: inherit; font-size: 0.9375rem; outline: none; } @@ -146,4 +184,25 @@ .save-btn:hover { background: #4338ca; +} + +.close-section { + border-top: 1px solid #eee; + padding-top: 1rem; + margin-top: 0.5rem; +} + +.field-row { + flex-direction: row; + align-items: center; + gap: 0.5rem; +} + +.field-row input[type="checkbox"] { + width: 1.125rem; + height: 1.125rem; +} + +.required { + color: #dc2626; } \ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.ts b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.ts index 1b04e65..9783624 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-card-full/goal-card-full.ts @@ -1,13 +1,14 @@ import { Component, inject, signal, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; +import { DatePipe } from '@angular/common'; import { Subscription } from 'rxjs'; import { StudentService } from '../../../shared/services/student.service'; import { StudentGoalItem } from '../../../shared/classes/student-goal'; @Component({ selector: 'app-goal-card-full', - imports: [FormsModule], + imports: [FormsModule, DatePipe], templateUrl: './goal-card-full.html', styleUrl: './goal-card-full.scss', }) @@ -42,6 +43,10 @@ export class GoalCardFull implements OnDestroy { protected description = ''; protected category = ''; protected baseline = ''; + protected targetCompletionDate: string | null = null; + protected closeDate: string | null = null; + protected achieved: boolean | null = null; + protected closeNotes: string | null = null; // Read-only metadata protected progressEventCount = 0; @@ -51,6 +56,10 @@ export class GoalCardFull implements OnDestroy { private savedDescription = ''; private savedCategory = ''; private savedBaseline = ''; + private savedTargetCompletionDate: string | null = null; + private savedCloseDate: string | null = null; + private savedAchieved: boolean | null = null; + private savedCloseNotes: string | null = null; // ************************** Properties *************************** @@ -60,7 +69,11 @@ export class GoalCardFull implements OnDestroy { hasChanges(): boolean { return this.description !== this.savedDescription || this.category !== this.savedCategory - || this.baseline !== this.savedBaseline; + || this.baseline !== this.savedBaseline + || this.targetCompletionDate !== this.savedTargetCompletionDate + || this.closeDate !== this.savedCloseDate + || this.achieved !== this.savedAchieved + || this.closeNotes !== this.savedCloseNotes; } // ************************ Public Methods ************************* @@ -79,6 +92,10 @@ export class GoalCardFull implements OnDestroy { description: this.description, category: this.category, baseline: this.baseline, + targetCompletionDate: this.targetCompletionDate, + closeDate: this.closeDate, + achieved: this.achieved, + closeNotes: this.closeNotes, }); this.saving.set(false); @@ -87,7 +104,12 @@ export class GoalCardFull implements OnDestroy { this.savedDescription = this.description; this.savedCategory = this.category; this.savedBaseline = this.baseline; + this.savedTargetCompletionDate = this.targetCompletionDate; + this.savedCloseDate = this.closeDate; + this.savedAchieved = this.achieved; + this.savedCloseNotes = this.closeNotes; this.successMessage.set('Changes saved.'); + this.studentService.notifyDataChanged(); } else { this.errorMessage.set(result.message); } @@ -100,6 +122,10 @@ export class GoalCardFull implements OnDestroy { this.description = this.savedDescription; this.category = this.savedCategory; this.baseline = this.savedBaseline; + this.targetCompletionDate = this.savedTargetCompletionDate; + this.closeDate = this.savedCloseDate; + this.achieved = this.savedAchieved; + this.closeNotes = this.savedCloseNotes; this.errorMessage.set(null); this.successMessage.set(null); } @@ -122,6 +148,14 @@ export class GoalCardFull implements OnDestroy { // ********************** Support Procedures *********************** + // ***************************************************************** + // Normalizes an API date string to YYYY-MM-DD for . + // ***************************************************************** + private toDateInput(value: string | null): string | null { + if (!value) return null; + return value.substring(0, 10); + } + // ***************************************************************** // Loads the goal by finding it in the student's goal list. // ***************************************************************** @@ -142,12 +176,20 @@ export class GoalCardFull implements OnDestroy { this.description = goal.description; this.category = goal.category; this.baseline = goal.baseline; + this.targetCompletionDate = this.toDateInput(goal.targetCompletionDate); + this.closeDate = this.toDateInput(goal.closeDate); + this.achieved = goal.achieved; + this.closeNotes = goal.closeNotes; this.progressEventCount = goal.progressEventCount; this.benchmarkCount = goal.benchmarkCount; this.savedDescription = goal.description; this.savedCategory = goal.category; this.savedBaseline = goal.baseline; + this.savedTargetCompletionDate = this.toDateInput(goal.targetCompletionDate); + this.savedCloseDate = this.toDateInput(goal.closeDate); + this.savedAchieved = goal.achieved; + this.savedCloseNotes = goal.closeNotes; this.loaded.set(true); }); } 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 086a307..65b39fa 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,14 +1,24 @@
- {{ goal().category }} - {{ goal().progressEventCount }} events + Goal: {{ goal().category }} + @if (goal().closeDate !== null) { + + {{ goal().achieved === true ? 'Closed ✓' : 'Closed ✗' }} + + } + @if (goal().targetCompletionDate) { + Target: {{ goal().targetCompletionDate | date:'mediumDate' }} + } @else { + No target date + }
-

{{ goal().category }}

-

{{ goal().description }}

+
+

{{ 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 2b9ff1f..4d8e6bd 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 @@ -6,64 +6,37 @@ .card { background: #fff; border-radius: 8px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); - padding: 1.25rem 1.5rem; + border: 1px solid #ddd; display: flex; flex-direction: column; - gap: 0.625rem; cursor: pointer; - min-width: 0; -} - -.card-footer { - display: flex; - align-items: center; - gap: 1.5rem; - margin: 0 -1.5rem -1rem; - padding: 0.5rem 1.5rem 0.5rem; - border-top: 1px solid #eee; -} - -.footer-link { - font-size: 0.875rem; - color: #4f46e5; - cursor: pointer; - text-decoration: underline; -} - -.footer-link:hover { - color: #4338ca; + overflow: hidden; } .card-header { display: flex; align-items: center; justify-content: space-between; + padding: 0.625rem 1.25rem; + background: #f8f9fa; + border-bottom: 1px solid #ddd; } -.category-badge { - padding: 0.2rem 0.6rem; - background: #eef2ff; - color: #4f46e5; - border-radius: 999px; - font-size: 0.75rem; +.card-title { + font-size: 0.875rem; font-weight: 600; + color: #333; } .event-count { - font-size: 0.8125rem; - color: #888; + font-size: 0.875rem; + font-weight: 600; + color: #333; } -.title { - margin: 0; - font-size: 1rem; - font-weight: 600; - color: #111; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.card-body { + padding: 1.25rem 1.5rem; } .description { @@ -72,7 +45,61 @@ color: #555; line-height: 1.5; display: -webkit-box; + line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; +} + +.card-footer { + display: flex; + align-items: center; + gap: 1.5rem; + padding: 0.5rem 1.5rem; + border-top: 1px solid #eee; +} + +.footer-btn { + padding: 0.375rem 0.75rem; + background: transparent; + border: 1px solid; + border-radius: 6px; + font-size: 0.8125rem; + font-weight: 500; + cursor: pointer; +} + +.btn-green { + color: #16a34a; + border-color: #16a34a; +} + +.btn-green:hover { + background: #f0fdf4; +} + +.btn-blue { + color: #2563eb; + border-color: #2563eb; + margin-left: auto; +} + +.btn-blue:hover { + background: #eff6ff; +} + +.closed-badge { + font-size: 0.75rem; + font-weight: 600; + padding: 0.125rem 0.5rem; + border-radius: 4px; + background: #fef2f2; + color: #dc2626; + border: 1px solid #fecaca; +} + +.closed-badge.achieved { + background: #f0fdf4; + color: #16a34a; + border-color: #bbf7d0; } \ 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 7fd7314..fb6e3cc 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,10 +1,11 @@ import { Component, inject, input } from '@angular/core'; +import { DatePipe } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { StudentGoalItem } from '../../../shared/classes/student-goal'; @Component({ selector: 'app-goal-card', - imports: [], + imports: [DatePipe], templateUrl: './goal-card.html', styleUrl: './goal-card.scss', }) 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 4651b68..d5bade3 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 @@ -13,7 +13,7 @@ } @if (showAddModal()) { - } 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 0cb53b3..ed009bf 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 @@ -52,7 +52,7 @@ font-size: 1.125rem; font-weight: 600; color: #333; - margin: 0 0 0.5rem; + margin: 0 0 1.25rem; } .spacer { @@ -78,6 +78,7 @@ .card-grid { display: flex; flex-wrap: wrap; + align-content: start; gap: 1rem; overflow-y: auto; flex: 1; diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.ts b/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.ts index 4023964..ef7989b 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/goal-list/goal-list.ts @@ -32,6 +32,7 @@ export class GoalList implements OnDestroy { protected studentId!: string; protected readonly studentIdentifier = signal(null); + protected readonly nextIepDate = signal(null); protected readonly goals = signal([]); protected readonly showAddModal = signal(false); protected readonly errorMessage = signal(null); @@ -71,6 +72,13 @@ export class GoalList implements OnDestroy { // Loads goals for the student from the service. // ***************************************************************** private loadGoals() { + this.studentService.getStudentById(this.studentId).then(studentResult => { + if (studentResult.success && studentResult.payload) { + const iep = studentResult.payload.nextIepDate; + this.nextIepDate.set(iep ? String(iep).substring(0, 10) : null); + } + }); + this.studentService.getGoalsForStudent(this.studentId).then(data => { if (!data.success) { this.errorMessage.set(data.message); diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.html b/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.html index 4d0273b..3dad196 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.html @@ -1 +1,57 @@ -

progress-edit works!

+
+ + {{ isNew() ? 'New Progress Event' : 'Edit Progress Event' }} + +
+ +@if (errorMessage()) { +

{{ errorMessage() }}

+} + +@if (loaded()) { +
+
+ Goal: {{ goalCategory }} +
+
+ + +
+ + @if (benchmarkItems().length > 0) { +
+ Associated Benchmarks +
+ @for (bm of benchmarkItems(); track bm.benchmarkId) { + + } +
+
+ } + + @if (!isNew()) { + + } +
+ + +
+
+} + +@if (successMessage()) { +

{{ successMessage() }}

+} diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.scss b/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.scss index e69de29..a8a391d 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.scss +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.scss @@ -0,0 +1,163 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} + +.toolbar { + display: flex; + align-items: center; + position: relative; + 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; +} + +.toolbar-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.back-btn { + margin-left: 0.5rem; +} + +.toolbar-title { + position: absolute; + left: 50%; + transform: translateX(-50%); + font-weight: 600; + font-size: 1.25rem; + color: #333; +} + +.spacer { + flex: 1; +} + +.error { + font-size: 0.875rem; + color: #dc2626; + margin: 0 0 1rem; +} + +.success { + font-size: 0.875rem; + color: #16a34a; + margin: 0 0 1rem; +} + +.detail-card { + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + padding: 1.5rem; + max-width: 600px; +} + +.field { + display: flex; + flex-direction: column; + margin-bottom: 1rem; +} + +.field-label { + font-size: 0.75rem; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.025em; + margin-bottom: 0.25rem; +} + +.field-input { + padding: 0.375rem 0.5rem; + border: 1px solid #ccc; + border-radius: 6px; + font-size: 0.9375rem; + outline: none; +} + +.field-input:focus { + border-color: #4f46e5; +} + +.field-textarea { + font-family: inherit; + resize: vertical; + min-height: 5rem; +} + +.benchmark-checks { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 0.25rem; +} + +.check-item { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.9375rem; + color: #333; + cursor: pointer; +} + +.check-item input[type="checkbox"] { + width: 1rem; + height: 1rem; + accent-color: #4f46e5; +} + +.metadata { + display: flex; + gap: 1.5rem; + margin-bottom: 1rem; +} + +.meta-item { + font-size: 0.8125rem; + color: #888; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.actions .toolbar-btn { + min-width: 6rem; +} + +.save-btn { + background: #4f46e5; + color: #fff; + border-color: #4f46e5; +} + +.save-btn:hover { + background: #4338ca; +} diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.ts b/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.ts index 478630d..3ad8691 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-edit/progress-edit.ts @@ -1,11 +1,228 @@ -import { Component } from '@angular/core'; +import { Component, inject, signal, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { DatePipe } from '@angular/common'; +import { Subscription } from 'rxjs'; +import { StudentService } from '../../../shared/services/student.service'; +import { BenchmarkDto } from '../../../shared/classes/benchmark.dto'; + +interface BenchmarkCheckItem { + benchmarkId: string; + label: string; + checked: boolean; +} @Component({ selector: 'app-progress-edit', - imports: [], + imports: [FormsModule, DatePipe], templateUrl: './progress-edit.html', styleUrl: './progress-edit.scss', }) -export class ProgressEdit { +export class ProgressEdit implements OnDestroy { + // ************************** Constructor ************************** + + constructor() { + this.paramSub = this.route.paramMap.subscribe(params => { + this.studentId = params.get('studentId')!; + this.goalId = params.get('goalId')!; + this.progressEventId = params.get('progressEventId') ?? null; + this.loadData(); + }); + } + + // ************************** Declarations ************************* + + private readonly studentService = inject(StudentService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly paramSub: Subscription; + + private studentId!: string; + private goalId!: string; + private progressEventId: string | null = null; + + protected readonly loaded = signal(false); + protected readonly isNew = signal(false); + protected readonly errorMessage = signal(null); + protected readonly successMessage = signal(null); + protected readonly saving = signal(false); + + // Form fields + protected content = ''; + private savedContent = ''; + + // Benchmark checkboxes + protected benchmarkItems = signal([]); + private savedBenchmarkSelections: Set = new Set(); + + // Read-only metadata + protected goalCategory = ''; + protected createdByName = ''; + protected createdAt: Date | null = null; + + // ************************** Properties *************************** + + // ***************************************************************** + // Returns true if the form has unsaved changes. + // ***************************************************************** + hasChanges(): boolean { + if (this.content !== this.savedContent) return true; + const current = new Set(this.benchmarkItems().filter(b => b.checked).map(b => b.benchmarkId)); + if (current.size !== this.savedBenchmarkSelections.size) return true; + for (const id of current) { + if (!this.savedBenchmarkSelections.has(id)) return true; + } + return false; + } + + // ************************ Public Methods ************************* + + // ************************ Event Handlers ************************* + + // ***************************************************************** + // Saves the progress event (create or update) and any benchmark + // associations based on the checked checkboxes. + // ***************************************************************** + async onSave() { + this.saving.set(true); + this.errorMessage.set(null); + this.successMessage.set(null); + + const checkedIds = this.benchmarkItems() + .filter(b => b.checked) + .map(b => b.benchmarkId); + + if (this.isNew()) { + const result = await this.studentService.addProgressEvent( + this.studentId, this.goalId, this.content.trim(), + checkedIds.length > 0 ? checkedIds : undefined + ); + this.saving.set(false); + if (result.success) { + this.successMessage.set('Progress event created.'); + this.savedContent = this.content; + this.savedBenchmarkSelections = new Set(checkedIds); + this.studentService.notifyDataChanged(); + if (result.payload?.progressEventId) { + this.router.navigate(['/students', this.studentId, 'goals', this.goalId, 'progress', result.payload.progressEventId]); + } + } else { + this.errorMessage.set(result.message); + } + } else { + const result = await this.studentService.updateProgressEvent( + this.studentId, this.progressEventId!, this.content.trim(), checkedIds + ); + this.saving.set(false); + if (result.success) { + this.savedContent = this.content; + this.savedBenchmarkSelections = new Set(checkedIds); + this.successMessage.set('Changes saved.'); + } else { + this.errorMessage.set(result.message); + } + } + } + + // ***************************************************************** + // Reverts the form to the last-saved state. + // ***************************************************************** + onCancel() { + this.content = this.savedContent; + this.benchmarkItems.update(items => + items.map(b => ({ ...b, checked: this.savedBenchmarkSelections.has(b.benchmarkId) })) + ); + this.errorMessage.set(null); + this.successMessage.set(null); + } + + onBack() { + this.router.navigate(['/students', this.studentId, 'goals', this.goalId, 'progress']); + } + + // ***************************************************************** + // Toggles a benchmark checkbox. + // ***************************************************************** + onToggleBenchmark(benchmarkId: string) { + this.benchmarkItems.update(items => + items.map(b => b.benchmarkId === benchmarkId ? { ...b, checked: !b.checked } : b) + ); + } + + ngOnDestroy() { + this.paramSub.unsubscribe(); + } + + // ********************** Support Procedures *********************** + + // ***************************************************************** + // Loads all data needed for the form: goal category, benchmarks, + // and (for edit mode) the existing event content + associations. + // ***************************************************************** + private async loadData() { + this.loaded.set(false); + + // Load goal category + const goalsResult = await this.studentService.getGoalsForStudent(this.studentId); + if (goalsResult.success && goalsResult.payload) { + const goal = goalsResult.payload.goals.find(g => g.goalId === this.goalId); + this.goalCategory = goal?.category ?? ''; + } + + // Load benchmarks for this goal + const bmResult = await this.studentService.getBenchmarksForStudent(this.studentId); + const goalBenchmarks = (bmResult.success && bmResult.payload) + ? bmResult.payload.benchmarks.filter(b => b.goalId === this.goalId) + : []; + + if (!this.progressEventId) { + // New event mode + this.isNew.set(true); + this.content = ''; + this.savedContent = ''; + this.savedBenchmarkSelections = new Set(); + this.benchmarkItems.set(goalBenchmarks.map(b => ({ + benchmarkId: b.benchmarkId, + label: b.shortName || b.benchmark, + checked: false, + }))); + this.loaded.set(true); + return; + } + + // Edit mode — load existing event + this.isNew.set(false); + const eventsResult = await this.studentService.getProgressEventsForGoal(this.goalId); + if (!eventsResult.success || !eventsResult.payload) { + this.errorMessage.set(eventsResult.message); + this.loaded.set(true); + return; + } + + const event = eventsResult.payload.find(e => e.progressEventId === this.progressEventId); + if (!event) { + this.errorMessage.set('Progress event not found.'); + this.loaded.set(true); + return; + } + + this.content = event.content; + this.savedContent = event.content; + this.createdByName = event.createdByName; + this.createdAt = event.createdAt; + + // Load existing benchmark associations + const assocResult = await this.studentService.getProgressEventBenchmarks(this.progressEventId!); + const associatedIds = new Set(assocResult.success && assocResult.payload ? assocResult.payload : []); + this.savedBenchmarkSelections = new Set(associatedIds); + + this.benchmarkItems.set(goalBenchmarks.map(b => ({ + benchmarkId: b.benchmarkId, + label: b.shortName || b.benchmark, + checked: associatedIds.has(b.benchmarkId), + }))); + + this.loaded.set(true); + } } 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 index 141f844..1ae849b 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.html @@ -1,7 +1,7 @@

{{ event().content }}

- +
{{ event().createdByName }} 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 index abd9d50..79e61a6 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.scss +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.scss @@ -47,15 +47,17 @@ text-align: right; } -// .icon-btn { -// background: none; -// border: none; -// cursor: pointer; -// font-size: 1rem; -// color: #888; -// padding: 0.125rem; -// } - -.icon-btn:hover { +.edit-btn { + padding: 0.25rem 0.625rem; + background: transparent; color: #4f46e5; + border: 1px solid #4f46e5; + border-radius: 6px; + font-size: 0.8125rem; + font-weight: 500; + cursor: pointer; +} + +.edit-btn:hover { + background: #eef2ff; } \ No newline at end of file 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 index 401e6f4..5ab52cc 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-item/progress-item.ts @@ -1,5 +1,6 @@ -import { Component, input } from '@angular/core'; +import { Component, inject, input } from '@angular/core'; import { DatePipe } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; import { ProgressEventDto } from '../../../shared/classes/progress-event.dto'; @Component({ @@ -14,6 +15,8 @@ export class ProgressItem { // ************************** Declarations ************************* + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); readonly event = input.required(); // ************************** Properties *************************** @@ -22,5 +25,9 @@ export class ProgressItem { // ************************ Event Handlers ************************* + onEdit() { + this.router.navigate([this.event().progressEventId], { relativeTo: this.route }); + } + // ********************** Support Procedures *********************** } 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 index 0435b5f..d2fa4a0 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/components/progress-list/progress-list.ts @@ -67,8 +67,7 @@ export class ProgressList implements OnDestroy { // ************************ Event Handlers ************************* onAddProgressEvent() { - this.showAddModal.set(true); - // TODO: Wire up add-progress-event modal component + this.router.navigate(['/students', this.studentId, 'goals', this.goalId, 'progress', 'new']); } // ***************************************************************** diff --git a/ui/winstudentgoaltracker/src/app/desktop/components/sidebar-tree-node/sidebar-tree-node.html b/ui/winstudentgoaltracker/src/app/desktop/components/sidebar-tree-node/sidebar-tree-node.html index 8c95338..b87834e 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/components/sidebar-tree-node/sidebar-tree-node.html +++ b/ui/winstudentgoaltracker/src/app/desktop/components/sidebar-tree-node/sidebar-tree-node.html @@ -2,6 +2,8 @@
@if (hasToggle(node)) { {{ node.expanded ? '−' : '+' }} + } @else { + } @if (node.routerLink) { -
- - -
-
- - +
+ Student
-
- - +
+
+ + +
+
+ + +
+ +
+ + +
\ No newline at end of file diff --git a/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts b/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts index ac235c7..45c5add 100644 --- a/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts +++ b/ui/winstudentgoaltracker/src/app/desktop/desktop.routes.ts @@ -7,6 +7,7 @@ import { GoalCardFull } from './components/goal-card-full/goal-card-full'; import { ProgressList } from './components/progress-list/progress-list'; import { BenchmarkList } from './components/benchmark-list/benchmark-list'; import { BenchmarkCardFull } from './components/benchmark-card-full/benchmark-card-full'; +import { ProgressEdit } from './components/progress-edit/progress-edit'; export default [ { @@ -19,6 +20,8 @@ export default [ { path: 'students/:studentId/goals', component: GoalList }, { path: 'students/:studentId/goals/:goalId', component: GoalCardFull }, { path: 'students/:studentId/goals/:goalId/progress', component: ProgressList }, + { path: 'students/:studentId/goals/:goalId/progress/new', component: ProgressEdit }, + { path: 'students/:studentId/goals/:goalId/progress/:progressEventId', component: ProgressEdit }, { path: 'students/:studentId/goals/:goalId/benchmarks', component: BenchmarkList }, { path: 'students/:studentId/goals/:goalId/benchmarks/new', component: BenchmarkCardFull }, { path: 'students/:studentId/goals/:goalId/benchmarks/:benchmarkId', component: BenchmarkCardFull }, diff --git a/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.html b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.html new file mode 100644 index 0000000..5772f12 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.html @@ -0,0 +1,4 @@ +
+ {{ checked() ? '✓' : '' }} + {{ label() }} +
diff --git a/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.scss b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.scss new file mode 100644 index 0000000..548ac97 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.scss @@ -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; +} diff --git a/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.spec.ts b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.spec.ts new file mode 100644 index 0000000..5e4dbd0 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToggleBenchmark } from './toggle-benchmark'; + +describe('ToggleBenchmark', () => { + let component: ToggleBenchmark; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ToggleBenchmark] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ToggleBenchmark); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.ts b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.ts new file mode 100644 index 0000000..bf3c907 --- /dev/null +++ b/ui/winstudentgoaltracker/src/app/mobile/components/toggle-benchmark/toggle-benchmark.ts @@ -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(); + readonly checked = input.required(); + readonly toggled = output(); + + // ************************ Event Handlers ************************* + + onTap() { + this.toggled.emit(); + } +} diff --git a/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.html b/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.html index 06886bb..3cd70d6 100644 --- a/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.html +++ b/ui/winstudentgoaltracker/src/app/mobile/pages/add-progress-event/add-progress-event.html @@ -15,11 +15,20 @@
- - -
+ @if (benchmarkItems().length > 0) { + +
+ @for (bm of benchmarkItems(); track bm.benchmarkId) { + + } +
+ } +