mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 05:17:41 +00:00
Renamed some of the goal fields to align with business logic
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"http": {
|
"http": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "http://localhost:5123",
|
"applicationUrl": "http://localhost:5123",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
|||||||
@@ -405,4 +405,51 @@ public class AuthController : BaseController
|
|||||||
Message = "Logged out successfully."
|
Message = "Logged out successfully."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *****************************************************************
|
||||||
|
// Sets the password hash and salt for an existing user.
|
||||||
|
// Accepts a user ID and plaintext password, hashes it, and stores
|
||||||
|
// the result in the user table.
|
||||||
|
// *****************************************************************
|
||||||
|
[HttpPost("SetPassword")]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status400BadRequest)]
|
||||||
|
public async Task<ActionResult<ResponseResult<object>>> SetPassword([FromBody] SetPasswordDto dto)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(dto.UserId) || string.IsNullOrWhiteSpace(dto.Password))
|
||||||
|
{
|
||||||
|
return BadRequest(new ResponseResult<object>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "User ID and password are required."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Guid.TryParse(dto.UserId, out Guid userId))
|
||||||
|
{
|
||||||
|
return BadRequest(new ResponseResult<object>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Invalid user ID format."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var (hash, salt) = PasswordHasher.HashPassword(dto.Password);
|
||||||
|
var updated = await _userRepo.SetPasswordAsync(userId, hash, salt);
|
||||||
|
|
||||||
|
if (!updated)
|
||||||
|
{
|
||||||
|
return Ok(new ResponseResult<object>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "User not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new ResponseResult<object>
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "Password set successfully."
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ namespace WinStudentGoalTracker.DataAccess;
|
|||||||
|
|
||||||
public class CreateGoalDto
|
public class CreateGoalDto
|
||||||
{
|
{
|
||||||
public string? Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
|
public string? Baseline { get; set; }
|
||||||
public Guid? GoalParentId { get; set; }
|
public Guid? GoalParentId { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace WinStudentGoalTracker.DataAccess;
|
||||||
|
|
||||||
|
public class SetPasswordDto
|
||||||
|
{
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string? Password { get; set; }
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ namespace WinStudentGoalTracker.DataAccess;
|
|||||||
|
|
||||||
public class UpdateGoalDto
|
public class UpdateGoalDto
|
||||||
{
|
{
|
||||||
public string? Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
|
public string? Baseline { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public class dbStudentBenchmarkRow
|
|||||||
public string? StudentIdentifier { get; set; }
|
public string? StudentIdentifier { get; set; }
|
||||||
public required Guid BenchmarkId { get; set; }
|
public required Guid BenchmarkId { get; set; }
|
||||||
public required Guid GoalId { get; set; }
|
public required Guid GoalId { get; set; }
|
||||||
public string? GoalTitle { get; set; }
|
public string? GoalCategory { get; set; }
|
||||||
public string? Benchmark { get; set; }
|
public string? Benchmark { get; set; }
|
||||||
public string? CreatedByName { get; set; }
|
public string? CreatedByName { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ public class dbStudentGoalRow
|
|||||||
public string? StudentIdentifier { get; set; }
|
public string? StudentIdentifier { get; set; }
|
||||||
public required Guid GoalId { get; set; }
|
public required Guid GoalId { get; set; }
|
||||||
public Guid? GoalParentId { get; set; }
|
public Guid? GoalParentId { get; set; }
|
||||||
public string? Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
|
public string? Baseline { get; set; }
|
||||||
public int ProgressEventCount { get; set; }
|
public int ProgressEventCount { get; set; }
|
||||||
public int BenchmarkCount { get; set; }
|
public int BenchmarkCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,9 +144,9 @@ public class StudentRepository
|
|||||||
p_id_goal_parent = dto.GoalParentId?.ToString(),
|
p_id_goal_parent = dto.GoalParentId?.ToString(),
|
||||||
p_id_student = idStudent.ToString(),
|
p_id_student = idStudent.ToString(),
|
||||||
p_id_user_created = userId.ToString(),
|
p_id_user_created = userId.ToString(),
|
||||||
p_title = dto.Title,
|
|
||||||
p_description = dto.Description,
|
p_description = dto.Description,
|
||||||
p_category = dto.Category
|
p_category = dto.Category,
|
||||||
|
p_baseline = dto.Baseline
|
||||||
},
|
},
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
@@ -156,9 +156,9 @@ public class StudentRepository
|
|||||||
{
|
{
|
||||||
GoalId = newGoalId,
|
GoalId = newGoalId,
|
||||||
GoalParentId = dto.GoalParentId,
|
GoalParentId = dto.GoalParentId,
|
||||||
Title = dto.Title,
|
|
||||||
Description = dto.Description,
|
Description = dto.Description,
|
||||||
Category = dto.Category,
|
Category = dto.Category,
|
||||||
|
Baseline = dto.Baseline,
|
||||||
ProgressEventCount = 0
|
ProgressEventCount = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -191,9 +191,9 @@ public class StudentRepository
|
|||||||
{
|
{
|
||||||
GoalId = r.GoalId,
|
GoalId = r.GoalId,
|
||||||
GoalParentId = r.GoalParentId,
|
GoalParentId = r.GoalParentId,
|
||||||
Title = r.Title,
|
|
||||||
Description = r.Description,
|
Description = r.Description,
|
||||||
Category = r.Category,
|
Category = r.Category,
|
||||||
|
Baseline = r.Baseline,
|
||||||
ProgressEventCount = r.ProgressEventCount,
|
ProgressEventCount = r.ProgressEventCount,
|
||||||
BenchmarkCount = r.BenchmarkCount
|
BenchmarkCount = r.BenchmarkCount
|
||||||
}).ToList()
|
}).ToList()
|
||||||
@@ -201,7 +201,7 @@ public class StudentRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
// Updates a goal's title, description, and category.
|
// Updates a goal's description, category, and baseline.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
public async Task<bool> UpdateGoalAsync(Guid goalId, UpdateGoalDto dto)
|
public async Task<bool> UpdateGoalAsync(Guid goalId, UpdateGoalDto dto)
|
||||||
{
|
{
|
||||||
@@ -214,9 +214,9 @@ public class StudentRepository
|
|||||||
p_id_goal_parent = (string?)null,
|
p_id_goal_parent = (string?)null,
|
||||||
p_id_student = (string?)null,
|
p_id_student = (string?)null,
|
||||||
p_id_user_created = (string?)null,
|
p_id_user_created = (string?)null,
|
||||||
p_title = dto.Title,
|
|
||||||
p_description = dto.Description,
|
p_description = dto.Description,
|
||||||
p_category = dto.Category
|
p_category = dto.Category,
|
||||||
|
p_baseline = dto.Baseline
|
||||||
},
|
},
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
@@ -254,7 +254,7 @@ public class StudentRepository
|
|||||||
{
|
{
|
||||||
BenchmarkId = r.BenchmarkId,
|
BenchmarkId = r.BenchmarkId,
|
||||||
GoalId = r.GoalId,
|
GoalId = r.GoalId,
|
||||||
GoalTitle = r.GoalTitle,
|
GoalCategory = r.GoalCategory,
|
||||||
Benchmark = r.Benchmark,
|
Benchmark = r.Benchmark,
|
||||||
CreatedByName = r.CreatedByName,
|
CreatedByName = r.CreatedByName,
|
||||||
CreatedAt = r.CreatedAt,
|
CreatedAt = r.CreatedAt,
|
||||||
|
|||||||
@@ -43,4 +43,23 @@ public class UserRepository
|
|||||||
new { p_id_user = idUser.ToString() },
|
new { p_id_user = idUser.ToString() },
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *****************************************************************
|
||||||
|
// Updates the password hash and salt for the given user.
|
||||||
|
// Returns true if the user was found and updated.
|
||||||
|
// *****************************************************************
|
||||||
|
public async Task<bool> SetPasswordAsync(Guid userId, string passwordHash, string passwordSalt)
|
||||||
|
{
|
||||||
|
using var db = Connection;
|
||||||
|
var rowsAffected = await db.QuerySingleOrDefaultAsync<int>(
|
||||||
|
"sp_User_SetPassword",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
p_id_user = userId.ToString(),
|
||||||
|
p_password_hash = passwordHash,
|
||||||
|
p_password_salt = passwordSalt
|
||||||
|
},
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ public class StudentBenchmarkItem
|
|||||||
{
|
{
|
||||||
public Guid BenchmarkId { get; set; }
|
public Guid BenchmarkId { get; set; }
|
||||||
public Guid GoalId { get; set; }
|
public Guid GoalId { get; set; }
|
||||||
public string? GoalTitle { get; set; }
|
public string? GoalCategory { get; set; }
|
||||||
public string? Benchmark { get; set; }
|
public string? Benchmark { get; set; }
|
||||||
public string? CreatedByName { get; set; }
|
public string? CreatedByName { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ public class StudentGoalItem
|
|||||||
{
|
{
|
||||||
public Guid GoalId { get; set; }
|
public Guid GoalId { get; set; }
|
||||||
public Guid? GoalParentId { get; set; }
|
public Guid? GoalParentId { get; set; }
|
||||||
public string? Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
|
public string? Baseline { get; set; }
|
||||||
public int ProgressEventCount { get; set; }
|
public int ProgressEventCount { get; set; }
|
||||||
public int BenchmarkCount { get; set; }
|
public int BenchmarkCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ BEGIN
|
|||||||
s.`identifier` AS `studentIdentifier`,
|
s.`identifier` AS `studentIdentifier`,
|
||||||
b.`id_benchmark` AS `benchmarkId`,
|
b.`id_benchmark` AS `benchmarkId`,
|
||||||
b.`id_goal` AS `goalId`,
|
b.`id_goal` AS `goalId`,
|
||||||
g.`title` AS `goalTitle`,
|
g.`category` AS `goalCategory`,
|
||||||
b.`benchmark` AS `benchmark`,
|
b.`benchmark` AS `benchmark`,
|
||||||
u.`name` AS `createdByName`,
|
u.`name` AS `createdByName`,
|
||||||
b.`created_at` AS `createdAt`,
|
b.`created_at` AS `createdAt`,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ BEGIN
|
|||||||
goalId,
|
goalId,
|
||||||
goalParentId,
|
goalParentId,
|
||||||
studentId,
|
studentId,
|
||||||
title,
|
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
|
baseline,
|
||||||
progressEventCount
|
progressEventCount
|
||||||
FROM v_goal_card
|
FROM v_goal_card
|
||||||
ORDER BY goalId;
|
ORDER BY goalId;
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ BEGIN
|
|||||||
goalId,
|
goalId,
|
||||||
goalParentId,
|
goalParentId,
|
||||||
studentId,
|
studentId,
|
||||||
title,
|
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
|
baseline,
|
||||||
progressEventCount
|
progressEventCount
|
||||||
FROM v_goal_card
|
FROM v_goal_card
|
||||||
WHERE goalId = p_id_goal
|
WHERE goalId = p_id_goal
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ BEGIN
|
|||||||
s.`identifier` AS `studentIdentifier`,
|
s.`identifier` AS `studentIdentifier`,
|
||||||
vc.`goalId`,
|
vc.`goalId`,
|
||||||
vc.`goalParentId`,
|
vc.`goalParentId`,
|
||||||
vc.`title`,
|
|
||||||
vc.`description`,
|
vc.`description`,
|
||||||
vc.`category`,
|
vc.`category`,
|
||||||
|
vc.`baseline`,
|
||||||
vc.`progressEventCount`,
|
vc.`progressEventCount`,
|
||||||
vc.`benchmarkCount`
|
vc.`benchmarkCount`
|
||||||
FROM `v_goal_card` vc
|
FROM `v_goal_card` vc
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_Insert`(
|
|||||||
IN p_id_goal_parent CHAR(36),
|
IN p_id_goal_parent CHAR(36),
|
||||||
IN p_id_student CHAR(36),
|
IN p_id_student CHAR(36),
|
||||||
IN p_id_user_created CHAR(36),
|
IN p_id_user_created CHAR(36),
|
||||||
IN p_title VARCHAR(255),
|
|
||||||
IN p_description TEXT,
|
IN p_description TEXT,
|
||||||
IN p_category VARCHAR(100)
|
IN p_category VARCHAR(255),
|
||||||
|
IN p_baseline TEXT
|
||||||
)
|
)
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO goal
|
INSERT INTO goal
|
||||||
@@ -15,9 +15,9 @@ BEGIN
|
|||||||
id_goal_parent,
|
id_goal_parent,
|
||||||
id_student,
|
id_student,
|
||||||
id_user_created,
|
id_user_created,
|
||||||
title,
|
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
|
baseline,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
@@ -27,9 +27,9 @@ BEGIN
|
|||||||
p_id_goal_parent,
|
p_id_goal_parent,
|
||||||
p_id_student,
|
p_id_student,
|
||||||
p_id_user_created,
|
p_id_user_created,
|
||||||
p_title,
|
|
||||||
p_description,
|
p_description,
|
||||||
p_category,
|
p_category,
|
||||||
|
p_baseline,
|
||||||
UTC_TIMESTAMP(),
|
UTC_TIMESTAMP(),
|
||||||
UTC_TIMESTAMP()
|
UTC_TIMESTAMP()
|
||||||
);
|
);
|
||||||
@@ -38,9 +38,9 @@ BEGIN
|
|||||||
id_goal_parent,
|
id_goal_parent,
|
||||||
id_student,
|
id_student,
|
||||||
id_user_created,
|
id_user_created,
|
||||||
title,
|
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
|
baseline,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM goal
|
FROM goal
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_Update`(
|
|||||||
IN p_id_goal_parent CHAR(36),
|
IN p_id_goal_parent CHAR(36),
|
||||||
IN p_id_student CHAR(36),
|
IN p_id_student CHAR(36),
|
||||||
IN p_id_user_created CHAR(36),
|
IN p_id_user_created CHAR(36),
|
||||||
IN p_title VARCHAR(255),
|
|
||||||
IN p_description TEXT,
|
IN p_description TEXT,
|
||||||
IN p_category VARCHAR(100)
|
IN p_category VARCHAR(255),
|
||||||
|
IN p_baseline TEXT
|
||||||
)
|
)
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE goal
|
UPDATE goal
|
||||||
@@ -14,9 +14,9 @@ BEGIN
|
|||||||
id_goal_parent = COALESCE(p_id_goal_parent, id_goal_parent),
|
id_goal_parent = COALESCE(p_id_goal_parent, id_goal_parent),
|
||||||
id_student = COALESCE(p_id_student, id_student),
|
id_student = COALESCE(p_id_student, id_student),
|
||||||
id_user_created = COALESCE(p_id_user_created, id_user_created),
|
id_user_created = COALESCE(p_id_user_created, id_user_created),
|
||||||
title = COALESCE(p_title, title),
|
|
||||||
description = COALESCE(p_description, description),
|
description = COALESCE(p_description, description),
|
||||||
category = COALESCE(p_category, category),
|
category = COALESCE(p_category, category),
|
||||||
|
baseline = COALESCE(p_baseline, baseline),
|
||||||
updated_at = UTC_TIMESTAMP()
|
updated_at = UTC_TIMESTAMP()
|
||||||
WHERE id_goal = p_id_goal;
|
WHERE id_goal = p_id_goal;
|
||||||
SELECT ROW_COUNT() AS rows_affected;
|
SELECT ROW_COUNT() AS rows_affected;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DELIMITER ;;
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `sp_User_SetPassword`(
|
||||||
|
IN p_id_user CHAR(36),
|
||||||
|
IN p_password_hash VARCHAR(255),
|
||||||
|
IN p_password_salt VARCHAR(255)
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
UPDATE user
|
||||||
|
SET password_hash = p_password_hash,
|
||||||
|
password_salt = p_password_salt,
|
||||||
|
password_updated_at = UTC_TIMESTAMP()
|
||||||
|
WHERE id_user = p_id_user;
|
||||||
|
SELECT ROW_COUNT() AS rows_affected;
|
||||||
|
END;;
|
||||||
|
DELIMITER ;
|
||||||
@@ -3,9 +3,9 @@ CREATE TABLE `goal` (
|
|||||||
`id_goal_parent` char(36) DEFAULT NULL,
|
`id_goal_parent` char(36) DEFAULT NULL,
|
||||||
`id_student` char(36) DEFAULT NULL,
|
`id_student` char(36) DEFAULT NULL,
|
||||||
`id_user_created` char(36) DEFAULT NULL,
|
`id_user_created` char(36) DEFAULT NULL,
|
||||||
`title` varchar(255) DEFAULT NULL,
|
|
||||||
`description` text,
|
`description` text,
|
||||||
`category` varchar(100) DEFAULT NULL,
|
`category` varchar(255) DEFAULT NULL,
|
||||||
|
`baseline` text,
|
||||||
`created_at` timestamp NULL DEFAULT NULL,
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
`updated_at` timestamp NULL DEFAULT NULL,
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
PRIMARY KEY (`id_goal`),
|
PRIMARY KEY (`id_goal`),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `winstudentgoaltracker`.`v_goal_card` AS
|
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`.`title` AS `title`,`g`.`description` AS `description`,`g`.`category` AS `category`,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`,count(distinct `pe`.`id_progress_event`) AS `progressEventCount`,count(distinct `b`.`id_benchmark`) AS `benchmarkCount`
|
||||||
from ((`winstudentgoaltracker`.`goal` `g`
|
from ((`winstudentgoaltracker`.`goal` `g`
|
||||||
left
|
left
|
||||||
join `winstudentgoaltracker`.`progress_event` `pe` on((`pe`.`id_goal` = `g`.`id_goal`)))
|
join `winstudentgoaltracker`.`progress_event` `pe` on((`pe`.`id_goal` = `g`.`id_goal`)))
|
||||||
left
|
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`.`title`,`g`.`description`,`g`.`category`;
|
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`;
|
||||||
|
|||||||
+23
-24
@@ -8,18 +8,6 @@
|
|||||||
|
|
||||||
<form class="modal-body" (ngSubmit)="onSubmit()" #goalForm="ngForm">
|
<form class="modal-body" (ngSubmit)="onSubmit()" #goalForm="ngForm">
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label for="title">Title<span class="required">*</span></label>
|
|
||||||
<input
|
|
||||||
id="title"
|
|
||||||
type="text"
|
|
||||||
[(ngModel)]="form.title"
|
|
||||||
name="title"
|
|
||||||
required
|
|
||||||
placeholder="e.g. Improve reading comprehension"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="category">Category<span class="required">*</span></label>
|
<label for="category">Category<span class="required">*</span></label>
|
||||||
<input
|
<input
|
||||||
@@ -32,18 +20,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (parentGoalOptions().length > 0) {
|
|
||||||
<div class="field">
|
|
||||||
<label for="goalParentId">Parent Goal <span class="optional">(optional)</span></label>
|
|
||||||
<select id="goalParentId" [(ngModel)]="form.goalParentId" name="goalParentId">
|
|
||||||
<option [ngValue]="null">None</option>
|
|
||||||
@for (goal of parentGoalOptions(); track goal.goalId) {
|
|
||||||
<option [ngValue]="goal.goalId">{{ goal.title }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="description">Description</label>
|
<label for="description">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -55,6 +31,29 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="baseline">Baseline</label>
|
||||||
|
<textarea
|
||||||
|
id="baseline"
|
||||||
|
[(ngModel)]="form.baseline"
|
||||||
|
name="baseline"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Enter baseline..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (parentGoalOptions().length > 0) {
|
||||||
|
<div class="field">
|
||||||
|
<label for="goalParentId">Parent Goal <span class="optional">(optional)</span></label>
|
||||||
|
<select id="goalParentId" [(ngModel)]="form.goalParentId" name="goalParentId">
|
||||||
|
<option [ngValue]="null">None</option>
|
||||||
|
@for (goal of parentGoalOptions(); track goal.goalId) {
|
||||||
|
<option [ngValue]="goal.goalId">{{ goal.category }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (errorMessage()) {
|
@if (errorMessage()) {
|
||||||
<p class="error">{{ errorMessage() }}</p>
|
<p class="error">{{ errorMessage() }}</p>
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@ export class AddGoalModal {
|
|||||||
);
|
);
|
||||||
|
|
||||||
protected form: CreateGoalDto = {
|
protected form: CreateGoalDto = {
|
||||||
title: '',
|
|
||||||
description: '',
|
description: '',
|
||||||
category: '',
|
category: '',
|
||||||
|
baseline: '',
|
||||||
goalParentId: null,
|
goalParentId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -9,14 +9,14 @@
|
|||||||
<form class="modal-body" (ngSubmit)="onSubmit()" #studentForm="ngForm">
|
<form class="modal-body" (ngSubmit)="onSubmit()" #studentForm="ngForm">
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="identifier">Identifier</label>
|
<label for="identifier">Student</label>
|
||||||
<input
|
<input
|
||||||
id="identifier"
|
id="identifier"
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="form.identifier"
|
[(ngModel)]="form.identifier"
|
||||||
name="identifier"
|
name="identifier"
|
||||||
required
|
required
|
||||||
placeholder="e.g. Student123"
|
placeholder="Initials or other non-personally identifiable label"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="field-label">Goal</span>
|
<span class="field-label">Goal</span>
|
||||||
<span class="field-value">{{ goalTitle }}</span>
|
<span class="field-value">{{ goalCategory }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label" for="benchmarkText">Benchmark</label>
|
<label class="field-label" for="benchmarkText">Benchmark</label>
|
||||||
|
|||||||
+6
-6
@@ -47,7 +47,7 @@ export class BenchmarkCardFull implements OnDestroy {
|
|||||||
private savedBenchmarkText = '';
|
private savedBenchmarkText = '';
|
||||||
|
|
||||||
// Read-only metadata
|
// Read-only metadata
|
||||||
protected goalTitle = '';
|
protected goalCategory = '';
|
||||||
protected createdByName = '';
|
protected createdByName = '';
|
||||||
protected createdAt: Date | null = null;
|
protected createdAt: Date | null = null;
|
||||||
protected updatedAt: Date | null = null;
|
protected updatedAt: Date | null = null;
|
||||||
@@ -127,7 +127,7 @@ export class BenchmarkCardFull implements OnDestroy {
|
|||||||
this.isNew.set(true);
|
this.isNew.set(true);
|
||||||
this.benchmarkText = '';
|
this.benchmarkText = '';
|
||||||
this.savedBenchmarkText = '';
|
this.savedBenchmarkText = '';
|
||||||
this.loadGoalTitle();
|
this.loadGoalCategory();
|
||||||
this.loaded.set(true);
|
this.loaded.set(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ export class BenchmarkCardFull implements OnDestroy {
|
|||||||
|
|
||||||
this.benchmarkText = bm.benchmark;
|
this.benchmarkText = bm.benchmark;
|
||||||
this.savedBenchmarkText = bm.benchmark;
|
this.savedBenchmarkText = bm.benchmark;
|
||||||
this.goalTitle = bm.goalTitle;
|
this.goalCategory = bm.goalCategory;
|
||||||
this.createdByName = bm.createdByName;
|
this.createdByName = bm.createdByName;
|
||||||
this.createdAt = bm.createdAt;
|
this.createdAt = bm.createdAt;
|
||||||
this.updatedAt = bm.updatedAt;
|
this.updatedAt = bm.updatedAt;
|
||||||
@@ -156,13 +156,13 @@ export class BenchmarkCardFull implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
// Loads the goal title for a new benchmark.
|
// Loads the goal category for a new benchmark.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
private loadGoalTitle() {
|
private loadGoalCategory() {
|
||||||
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||||
if (result.success && result.payload) {
|
if (result.success && result.payload) {
|
||||||
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||||
this.goalTitle = goal?.title ?? '';
|
this.goalCategory = goal?.category ?? '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="goal-badge">{{ benchmark().goalTitle }}</span>
|
<span class="goal-badge">{{ benchmark().goalCategory }}</span>
|
||||||
@if (benchmark().updatedAt) {
|
@if (benchmark().updatedAt) {
|
||||||
<span class="date">Updated: {{ benchmark().updatedAt | date:'M/d/yy' }}</span>
|
<span class="date">Updated: {{ benchmark().updatedAt | date:'M/d/yy' }}</span>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
+9
-4
@@ -4,8 +4,10 @@
|
|||||||
<button class="toolbar-btn" (click)="onAddBenchmark()">+ Add a Benchmark</button>
|
<button class="toolbar-btn" (click)="onAddBenchmark()">+ Add a Benchmark</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (studentIdentifier()) {
|
@if (goalCategory()) {
|
||||||
<h2 class="section-header">Benchmarks for {{ studentIdentifier() }}</h2>
|
<h2 class="section-header">Benchmarks for {{ goalCategory() }}</h2>
|
||||||
|
} @else {
|
||||||
|
<h2 class="section-header">Benchmarks</h2>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (errorMessage()) {
|
@if (errorMessage()) {
|
||||||
@@ -13,8 +15,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (benchmarks().length === 0 && !errorMessage()) {
|
@if (benchmarks().length === 0 && !errorMessage()) {
|
||||||
<p class="empty-state">No benchmarks yet. Click <a class="empty-link" (click)="onAddBenchmark()">Add a Benchmark</a> to
|
<div class="empty-state">
|
||||||
get started.</p>
|
<p>Benchmarks are milestones on the way to achieving a goal. They are optional in this system.</p>
|
||||||
|
<p>No benchmarks yet.</p>
|
||||||
|
<p>Click <strong>+ Add a Benchmark</strong> in the upper right to get started.</p>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
@for (bm of benchmarks(); track bm.benchmarkId) {
|
@for (bm of benchmarks(); track bm.benchmarkId) {
|
||||||
|
|||||||
+6
-9
@@ -54,16 +54,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
color: #888;
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
margin: 2rem auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
padding: 3rem 1.5rem;
|
||||||
|
color: #555;
|
||||||
.empty-link {
|
font-size: 0.9375rem;
|
||||||
color: #4f46e5;
|
background: #fff;
|
||||||
text-decoration: underline;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
border: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
|
|||||||
+35
-5
@@ -18,6 +18,7 @@ export class BenchmarkList {
|
|||||||
this.studentId = this.route.snapshot.paramMap.get('studentId')!;
|
this.studentId = this.route.snapshot.paramMap.get('studentId')!;
|
||||||
this.goalId = this.route.snapshot.paramMap.get('goalId') || '';
|
this.goalId = this.route.snapshot.paramMap.get('goalId') || '';
|
||||||
this.loadBenchmarks();
|
this.loadBenchmarks();
|
||||||
|
this.loadGoalCategory();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ************************** Declarations *************************
|
// ************************** Declarations *************************
|
||||||
@@ -28,7 +29,7 @@ export class BenchmarkList {
|
|||||||
|
|
||||||
protected readonly studentId: string;
|
protected readonly studentId: string;
|
||||||
protected readonly goalId: string;
|
protected readonly goalId: string;
|
||||||
protected readonly studentIdentifier = signal<string | null>(null);
|
protected readonly goalCategory = signal<string | null>(null);
|
||||||
protected readonly benchmarks = signal<BenchmarkDto[]>([]);
|
protected readonly benchmarks = signal<BenchmarkDto[]>([]);
|
||||||
protected readonly errorMessage = signal<string | null>(null);
|
protected readonly errorMessage = signal<string | null>(null);
|
||||||
|
|
||||||
@@ -43,22 +44,51 @@ export class BenchmarkList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBack() {
|
onBack() {
|
||||||
this.router.navigate(['/students', this.studentId, 'goals', this.goalId]);
|
if (this.goalId) {
|
||||||
|
this.router.navigate(['/students', this.studentId, 'goals', this.goalId]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/students', this.studentId, 'goals']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********************** Support Procedures ***********************
|
// ********************** Support Procedures ***********************
|
||||||
|
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
// Loads benchmarks for the student from the service.
|
// Loads benchmarks for the student from the service. Also sets
|
||||||
|
// goalCategory from the first benchmark's data.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
private loadBenchmarks() {
|
private loadBenchmarks() {
|
||||||
this.studentService.getBenchmarksForStudent(this.studentId).then(data => {
|
this.studentService.getBenchmarksForStudent(this.studentId).then(data => {
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
this.errorMessage.set(data.message);
|
this.errorMessage.set(data.message);
|
||||||
} else {
|
} else {
|
||||||
this.studentIdentifier.set(data.payload?.studentIdentifier ?? null);
|
const benchmarks = data.payload?.benchmarks ?? [];
|
||||||
this.benchmarks.set(data.payload?.benchmarks ?? []);
|
this.benchmarks.set(benchmarks);
|
||||||
|
|
||||||
|
// Set the goal category from benchmark data if available
|
||||||
|
if (this.goalId && benchmarks.length > 0) {
|
||||||
|
const match = benchmarks.find(b => b.goalId === this.goalId);
|
||||||
|
if (match) this.goalCategory.set(match.goalCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still don't have a category, load it from the goals API
|
||||||
|
if (!this.goalCategory()) {
|
||||||
|
this.loadGoalCategory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *****************************************************************
|
||||||
|
// Loads the goal category from the goals API as a fallback when
|
||||||
|
// no benchmarks exist yet to extract it from.
|
||||||
|
// *****************************************************************
|
||||||
|
private loadGoalCategory() {
|
||||||
|
if (!this.goalId) return;
|
||||||
|
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||||
|
if (!result.success || !result.payload) return;
|
||||||
|
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||||
|
this.goalCategory.set(goal?.category ?? null);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button class="toolbar-btn back-btn" (click)="onBack()">↑ Student</button>
|
<button class="toolbar-btn back-btn" (click)="onBack()">↑ Goals</button>
|
||||||
<span class="toolbar-title">Goal Detail</span>
|
<span class="toolbar-title">Goal Detail</span>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
@if (loaded()) {
|
@if (loaded()) {
|
||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label" for="title">Title</label>
|
<label class="field-label" for="baseline">Baseline</label>
|
||||||
<input id="title" class="field-input" type="text" [(ngModel)]="title" />
|
<input id="baseline" class="field-input" type="text" [(ngModel)]="baseline" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label" for="description">Description</label>
|
<label class="field-label" for="description">Description</label>
|
||||||
|
|||||||
+11
-11
@@ -39,18 +39,18 @@ export class GoalCardFull implements OnDestroy {
|
|||||||
protected readonly saving = signal(false);
|
protected readonly saving = signal(false);
|
||||||
|
|
||||||
// Form fields
|
// Form fields
|
||||||
protected title = '';
|
|
||||||
protected description = '';
|
protected description = '';
|
||||||
protected category = '';
|
protected category = '';
|
||||||
|
protected baseline = '';
|
||||||
|
|
||||||
// Read-only metadata
|
// Read-only metadata
|
||||||
protected progressEventCount = 0;
|
protected progressEventCount = 0;
|
||||||
protected benchmarkCount = 0;
|
protected benchmarkCount = 0;
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
private savedTitle = '';
|
|
||||||
private savedDescription = '';
|
private savedDescription = '';
|
||||||
private savedCategory = '';
|
private savedCategory = '';
|
||||||
|
private savedBaseline = '';
|
||||||
|
|
||||||
// ************************** Properties ***************************
|
// ************************** Properties ***************************
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ export class GoalCardFull implements OnDestroy {
|
|||||||
// Returns true if form values differ from the saved snapshot.
|
// Returns true if form values differ from the saved snapshot.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
hasChanges(): boolean {
|
hasChanges(): boolean {
|
||||||
return this.title !== this.savedTitle
|
return this.description !== this.savedDescription
|
||||||
|| this.description !== this.savedDescription
|
|| this.category !== this.savedCategory
|
||||||
|| this.category !== this.savedCategory;
|
|| this.baseline !== this.savedBaseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ************************ Public Methods *************************
|
// ************************ Public Methods *************************
|
||||||
@@ -76,17 +76,17 @@ export class GoalCardFull implements OnDestroy {
|
|||||||
this.successMessage.set(null);
|
this.successMessage.set(null);
|
||||||
|
|
||||||
const result = await this.studentService.updateGoal(this.studentId, this.goalId, {
|
const result = await this.studentService.updateGoal(this.studentId, this.goalId, {
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
description: this.description,
|
||||||
category: this.category,
|
category: this.category,
|
||||||
|
baseline: this.baseline,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.saving.set(false);
|
this.saving.set(false);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.savedTitle = this.title;
|
|
||||||
this.savedDescription = this.description;
|
this.savedDescription = this.description;
|
||||||
this.savedCategory = this.category;
|
this.savedCategory = this.category;
|
||||||
|
this.savedBaseline = this.baseline;
|
||||||
this.successMessage.set('Changes saved.');
|
this.successMessage.set('Changes saved.');
|
||||||
} else {
|
} else {
|
||||||
this.errorMessage.set(result.message);
|
this.errorMessage.set(result.message);
|
||||||
@@ -97,15 +97,15 @@ export class GoalCardFull implements OnDestroy {
|
|||||||
// Reverts form fields to the last-saved snapshot.
|
// Reverts form fields to the last-saved snapshot.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
onCancel() {
|
onCancel() {
|
||||||
this.title = this.savedTitle;
|
|
||||||
this.description = this.savedDescription;
|
this.description = this.savedDescription;
|
||||||
this.category = this.savedCategory;
|
this.category = this.savedCategory;
|
||||||
|
this.baseline = this.savedBaseline;
|
||||||
this.errorMessage.set(null);
|
this.errorMessage.set(null);
|
||||||
this.successMessage.set(null);
|
this.successMessage.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBack() {
|
onBack() {
|
||||||
this.router.navigate(['/students', this.studentId]);
|
this.router.navigate(['/students', this.studentId, 'goals']);
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgressEvents() {
|
onProgressEvents() {
|
||||||
@@ -139,15 +139,15 @@ export class GoalCardFull implements OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.title = goal.title;
|
|
||||||
this.description = goal.description;
|
this.description = goal.description;
|
||||||
this.category = goal.category;
|
this.category = goal.category;
|
||||||
|
this.baseline = goal.baseline;
|
||||||
this.progressEventCount = goal.progressEventCount;
|
this.progressEventCount = goal.progressEventCount;
|
||||||
this.benchmarkCount = goal.benchmarkCount;
|
this.benchmarkCount = goal.benchmarkCount;
|
||||||
|
|
||||||
this.savedTitle = goal.title;
|
|
||||||
this.savedDescription = goal.description;
|
this.savedDescription = goal.description;
|
||||||
this.savedCategory = goal.category;
|
this.savedCategory = goal.category;
|
||||||
|
this.savedBaseline = goal.baseline;
|
||||||
this.loaded.set(true);
|
this.loaded.set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<span class="event-count">{{ goal().progressEventCount }} events</span>
|
<span class="event-count">{{ goal().progressEventCount }} events</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="title">{{ goal().title }}</h3>
|
<h3 class="title">{{ goal().category }}</h3>
|
||||||
<p class="description">{{ goal().description }}</p>
|
<p class="description">{{ goal().description }}</p>
|
||||||
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
width: 300px;
|
width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (goals().length === 0 && !errorMessage()) {
|
@if (goals().length === 0 && !errorMessage()) {
|
||||||
<p class="empty-state">No goals yet. Click <strong>+ Add a Goal</strong> to get started.</p>
|
<div class="empty-state">
|
||||||
|
<p>No goals yet.</p>
|
||||||
|
<p>Click <strong>+ Add a Goal</strong> in the upper right to get started.</p>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
@for (goal of goals(); track goal.goalId) {
|
@for (goal of goals(); track goal.goalId) {
|
||||||
|
|||||||
@@ -66,10 +66,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
color: #888;
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
margin: 2rem auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
@@ -88,4 +91,5 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
border-top: 1px solid #ddd;
|
border-top: 1px solid #ddd;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,7 @@ export class GoalList implements OnDestroy {
|
|||||||
onGoalCreated(goal: StudentGoalItem) {
|
onGoalCreated(goal: StudentGoalItem) {
|
||||||
this.goals.update(list => [...list, goal]);
|
this.goals.update(list => [...list, goal]);
|
||||||
this.showAddModal.set(false);
|
this.showAddModal.set(false);
|
||||||
|
this.studentService.notifyDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onModalCancelled() {
|
onModalCancelled() {
|
||||||
|
|||||||
+2
-2
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
<!-- <img class="hero-image" src="/slalomcropped.png" alt="Slalom" /> -->
|
<!-- <img class="hero-image" src="/slalomcropped.png" alt="Slalom" /> -->
|
||||||
|
|
||||||
@if (studentIdentifier() && goalTitle()) {
|
@if (studentIdentifier() && goalCategory()) {
|
||||||
<div class="header-row">
|
<div class="header-row">
|
||||||
<h2 class="section-header">
|
<h2 class="section-header">
|
||||||
Student: {{ studentIdentifier() }} Goal: {{ goalTitle() }}
|
Student: {{ studentIdentifier() }} Goal: {{ goalCategory() }}
|
||||||
@if (isFiltered()) {
|
@if (isFiltered()) {
|
||||||
<span class="filter-count">(showing {{ filteredEvents().length }} of {{ events().length }})</span>
|
<span class="filter-count">(showing {{ filteredEvents().length }} of {{ events().length }})</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class ProgressList implements OnDestroy {
|
|||||||
this.studentId = this.route.snapshot.paramMap.get('studentId')!;
|
this.studentId = this.route.snapshot.paramMap.get('studentId')!;
|
||||||
this.goalId = this.route.snapshot.paramMap.get('goalId')!;
|
this.goalId = this.route.snapshot.paramMap.get('goalId')!;
|
||||||
this.loadEvents();
|
this.loadEvents();
|
||||||
this.loadGoalTitle();
|
this.loadGoalCategory();
|
||||||
|
|
||||||
this.searchInput$.pipe(debounceTime(300)).subscribe(term => {
|
this.searchInput$.pipe(debounceTime(300)).subscribe(term => {
|
||||||
this.searchTerm.set(term);
|
this.searchTerm.set(term);
|
||||||
@@ -38,7 +38,7 @@ export class ProgressList implements OnDestroy {
|
|||||||
private readonly searchInput$ = new Subject<string>();
|
private readonly searchInput$ = new Subject<string>();
|
||||||
|
|
||||||
protected readonly studentIdentifier = signal<string | null>(null);
|
protected readonly studentIdentifier = signal<string | null>(null);
|
||||||
protected readonly goalTitle = signal<string | null>(null);
|
protected readonly goalCategory = signal<string | null>(null);
|
||||||
protected readonly events = signal<ProgressEventDto[]>([]);
|
protected readonly events = signal<ProgressEventDto[]>([]);
|
||||||
protected readonly errorMessage = signal<string | null>(null);
|
protected readonly errorMessage = signal<string | null>(null);
|
||||||
protected readonly rawSearchText = signal('');
|
protected readonly rawSearchText = signal('');
|
||||||
@@ -118,16 +118,15 @@ export class ProgressList implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
// Loads the goal title from the student's goal list so the heading
|
// Loads the goal category from the student's goal list so the heading
|
||||||
// can display "Progress for <goal title>".
|
// can display "Progress for <goal category>".
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
private loadGoalTitle() {
|
private loadGoalCategory() {
|
||||||
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||||
if (result.success && result.payload) {
|
if (!result.success || !result.payload) return;
|
||||||
this.studentIdentifier.set(result.payload.studentIdentifier);
|
this.studentIdentifier.set(result.payload.studentIdentifier);
|
||||||
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
const goal = result.payload.goals.find(g => g.goalId === this.goalId);
|
||||||
this.goalTitle.set(goal?.title ?? null);
|
this.goalCategory.set(goal?.category ?? null);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -44,6 +44,7 @@ export class StudentCardList {
|
|||||||
onStudentCreated(student: StudentCardDto) {
|
onStudentCreated(student: StudentCardDto) {
|
||||||
this.students.update(list => this.sortByIdentifier([...list, student]));
|
this.students.update(list => this.sortByIdentifier([...list, student]));
|
||||||
this.showAddModal.set(false);
|
this.showAddModal.set(false);
|
||||||
|
this.studentService.notifyDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onModalCancelled() {
|
onModalCancelled() {
|
||||||
|
|||||||
@@ -106,6 +106,8 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export class Home implements OnDestroy {
|
|||||||
if (!result.success || !result.payload) return [];
|
if (!result.success || !result.payload) return [];
|
||||||
|
|
||||||
return result.payload.goals.map(goal => ({
|
return result.payload.goals.map(goal => ({
|
||||||
label: goal.title,
|
label: goal.category,
|
||||||
routerLink: ['/students', studentId, 'goals', goal.goalId],
|
routerLink: ['/students', studentId, 'goals', goal.goalId],
|
||||||
childCount: 2,
|
childCount: 2,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<button class="back-btn" (click)="onBack()">← Back</button>
|
<button class="back-btn" (click)="onBack()">← Back</button>
|
||||||
<span class="student-name">{{ studentIdentifier() }}</span>
|
<span class="student-name">{{ studentIdentifier() }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="goal-title">{{ goalTitle() }}</h2>
|
<h2 class="goal-title">{{ goalCategory() }}</h2>
|
||||||
|
|
||||||
<!-- Error message -->
|
<!-- Error message -->
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ export class AddProgressEvent {
|
|||||||
// ************************** Constructor **************************
|
// ************************** Constructor **************************
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.goalTitle.set(this.route.snapshot.queryParamMap.get('goalTitle') ?? '');
|
this.goalCategory.set(this.route.snapshot.queryParamMap.get('goalCategory') ?? '');
|
||||||
this.studentIdentifier.set(this.route.snapshot.queryParamMap.get('studentIdentifier') ?? '');
|
this.studentIdentifier.set(this.route.snapshot.queryParamMap.get('studentIdentifier') ?? '');
|
||||||
this.studentId = this.route.snapshot.paramMap.get('studentId') ?? '';
|
this.studentId = this.route.snapshot.paramMap.get('studentId') ?? '';
|
||||||
this.goalId = this.route.snapshot.paramMap.get('goalId') ?? '';
|
this.goalId = this.route.snapshot.paramMap.get('goalId') ?? '';
|
||||||
@@ -32,7 +32,7 @@ export class AddProgressEvent {
|
|||||||
private readonly studentId: string;
|
private readonly studentId: string;
|
||||||
private readonly goalId: string;
|
private readonly goalId: string;
|
||||||
|
|
||||||
protected readonly goalTitle = signal('');
|
protected readonly goalCategory = signal('');
|
||||||
protected readonly studentIdentifier = signal('');
|
protected readonly studentIdentifier = signal('');
|
||||||
protected readonly notes = signal('');
|
protected readonly notes = signal('');
|
||||||
protected readonly error = signal<string | null>(null);
|
protected readonly error = signal<string | null>(null);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<h2 class="section-heading">Goals</h2>
|
<h2 class="section-heading">Goals</h2>
|
||||||
<div class="goal-cards">
|
<div class="goal-cards">
|
||||||
@for (goal of data()?.goals; track goal.goalId) {
|
@for (goal of data()?.goals; track goal.goalId) {
|
||||||
<div class="goal-card" (click)="onGoalClick(goal.goalId, goal.title)">
|
<div class="goal-card" (click)="onGoalClick(goal.goalId, goal.category)">
|
||||||
<span class="goal-title">{{ goal.title }}</span>
|
<span class="goal-title">{{ goal.category }}</span>
|
||||||
<span class="event-count">{{ goal.progressEventCount }}</span>
|
<span class="event-count">{{ goal.progressEventCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ export class StudentGoals {
|
|||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
// Navigates to the add-progress-event page for the selected goal.
|
// Navigates to the add-progress-event page for the selected goal.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
onGoalClick(goalId: string, goalTitle: string) {
|
onGoalClick(goalId: string, goalCategory: string) {
|
||||||
this.router.navigate(
|
this.router.navigate(
|
||||||
['students', this.studentId, 'goals', goalId, 'add-event'],
|
['students', this.studentId, 'goals', goalId, 'add-event'],
|
||||||
{ queryParams: { goalTitle, studentIdentifier: this.data()?.studentIdentifier } },
|
{ queryParams: { goalCategory, studentIdentifier: this.data()?.studentIdentifier } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface StudentBenchmarkSummary {
|
|||||||
export interface BenchmarkDto {
|
export interface BenchmarkDto {
|
||||||
benchmarkId: string;
|
benchmarkId: string;
|
||||||
goalId: string;
|
goalId: string;
|
||||||
goalTitle: string;
|
goalCategory: string;
|
||||||
benchmark: string;
|
benchmark: string;
|
||||||
createdByName: string;
|
createdByName: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface CreateGoalDto {
|
export interface CreateGoalDto {
|
||||||
title: string;
|
|
||||||
description: string;
|
description: string;
|
||||||
category: string;
|
category: string;
|
||||||
|
baseline: string;
|
||||||
goalParentId: string | null;
|
goalParentId: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ export interface StudentGoalSummary {
|
|||||||
export interface StudentGoalItem {
|
export interface StudentGoalItem {
|
||||||
goalId: string; // goal.id_goal — char(36)
|
goalId: string; // goal.id_goal — char(36)
|
||||||
goalParentId: string | null;
|
goalParentId: string | null;
|
||||||
title: string; // goal.title — varchar(255)
|
|
||||||
description: string; // goal.description — text
|
description: string; // goal.description — text
|
||||||
category: string; // goal.category — varchar(100)
|
category: string; // goal.category — varchar(100)
|
||||||
|
baseline: string; // goal.baseline — text
|
||||||
progressEventCount: number; // count of progress_event rows for this goal
|
progressEventCount: number; // count of progress_event rows for this goal
|
||||||
benchmarkCount: number; // count of benchmark rows for this goal
|
benchmarkCount: number; // count of benchmark rows for this goal
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,41 +19,41 @@ export class DummyStudentService {
|
|||||||
'1': {
|
'1': {
|
||||||
studentIdentifier: 'J.B',
|
studentIdentifier: 'J.B',
|
||||||
goals: [
|
goals: [
|
||||||
{ goalId: 'g1', goalParentId: null, title: 'Improve reading comprehension', description: 'Work on main-idea identification and inference skills across fiction and nonfiction texts.', category: 'Academics', progressEventCount: 5, benchmarkCount: 2 },
|
{ goalId: 'g1', goalParentId: null, description: 'Work on main-idea identification and inference skills across fiction and nonfiction texts.', category: 'Academics', baseline: '', progressEventCount: 5, benchmarkCount: 2 },
|
||||||
{ goalId: 'g2', goalParentId: null, title: 'Complete algebra module', description: 'Finish all units in the algebra course including linear equations and graphing.', category: 'Academics', progressEventCount: 2, benchmarkCount: 0 },
|
{ goalId: 'g2', goalParentId: null, description: 'Finish all units in the algebra course including linear equations and graphing.', category: 'Academics', baseline: '', progressEventCount: 2, benchmarkCount: 0 },
|
||||||
{ goalId: 'g3', goalParentId: null, title: 'Weekly journal entries', description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', progressEventCount: 8, benchmarkCount: 1 },
|
{ goalId: 'g3', goalParentId: null, description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', baseline: '', progressEventCount: 8, benchmarkCount: 1 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
studentIdentifier: 'M.K',
|
studentIdentifier: 'M.K',
|
||||||
goals: [
|
goals: [
|
||||||
{ goalId: 'g4', goalParentId: null, title: 'Pass certification exam', description: 'Prepare for and pass the industry certification exam by end of quarter.', category: 'Career Readiness', progressEventCount: 3, benchmarkCount: 0 },
|
{ goalId: 'g4', goalParentId: null, description: 'Prepare for and pass the industry certification exam by end of quarter.', category: 'Career Readiness', baseline: '', progressEventCount: 3, benchmarkCount: 0 },
|
||||||
{ goalId: 'g5', goalParentId: null, title: 'Attendance above 90%', description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', progressEventCount: 0, benchmarkCount: 0 },
|
{ goalId: 'g5', goalParentId: null, description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', baseline: '', progressEventCount: 0, benchmarkCount: 0 },
|
||||||
{ goalId: 'g6', goalParentId: null, title: 'Complete internship hours', description: 'Log the required 40 hours at the assigned internship site.', category: 'Career Readiness', progressEventCount: 12, benchmarkCount: 0 },
|
{ goalId: 'g6', goalParentId: null, description: 'Log the required 40 hours at the assigned internship site.', category: 'Career Readiness', baseline: '', progressEventCount: 12, benchmarkCount: 0 },
|
||||||
{ goalId: 'g7', goalParentId: null, title: 'Portfolio project', description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', progressEventCount: 1, benchmarkCount: 0 },
|
{ goalId: 'g7', goalParentId: null, description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', baseline: '', progressEventCount: 1, benchmarkCount: 0 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'3': {
|
'3': {
|
||||||
studentIdentifier: 'A.R',
|
studentIdentifier: 'A.R',
|
||||||
goals: [
|
goals: [
|
||||||
{ goalId: 'g8', goalParentId: null, title: 'GED preparation', description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', progressEventCount: 6, benchmarkCount: 0 },
|
{ goalId: 'g8', goalParentId: null, description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', baseline: '', progressEventCount: 6, benchmarkCount: 0 },
|
||||||
{ goalId: 'g9', goalParentId: null, title: 'Resume workshop', description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', progressEventCount: 0, benchmarkCount: 0 },
|
{ goalId: 'g9', goalParentId: null, description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', baseline: '', progressEventCount: 0, benchmarkCount: 0 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'4': {
|
'4': {
|
||||||
studentIdentifier: 'T.W',
|
studentIdentifier: 'T.W',
|
||||||
goals: [
|
goals: [
|
||||||
{ goalId: 'g10', goalParentId: null, title: 'Public speaking practice', description: 'Present in front of the class at least once per month.', category: 'Communication', progressEventCount: 4, benchmarkCount: 0 },
|
{ goalId: 'g10', goalParentId: null, description: 'Present in front of the class at least once per month.', category: 'Communication', baseline: '', progressEventCount: 4, benchmarkCount: 0 },
|
||||||
{ goalId: 'g11', goalParentId: null, title: 'Math placement improvement', description: 'Move up one placement level in math by the end of the semester.', category: 'Academics', progressEventCount: 7, benchmarkCount: 0 },
|
{ goalId: 'g11', goalParentId: null, description: 'Move up one placement level in math by the end of the semester.', category: 'Academics', baseline: '', progressEventCount: 7, benchmarkCount: 0 },
|
||||||
{ goalId: 'g12', goalParentId: null, title: 'Conflict resolution strategies', description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', progressEventCount: 2, benchmarkCount: 0 },
|
{ goalId: 'g12', goalParentId: null, description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', baseline: '', progressEventCount: 2, benchmarkCount: 0 },
|
||||||
{ goalId: 'g13', goalParentId: null, title: 'Daily attendance streak', description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', progressEventCount: 0, benchmarkCount: 0 },
|
{ goalId: 'g13', goalParentId: null, description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', baseline: '', progressEventCount: 0, benchmarkCount: 0 },
|
||||||
{ goalId: 'g14', goalParentId: null, title: 'Job shadow experience', description: 'Complete a job shadow day in a field of interest.', category: 'Career Readiness', progressEventCount: 1, benchmarkCount: 0 },
|
{ goalId: 'g14', goalParentId: null, description: 'Complete a job shadow day in a field of interest.', category: 'Career Readiness', baseline: '', progressEventCount: 1, benchmarkCount: 0 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'5': {
|
'5': {
|
||||||
studentIdentifier: 'L.C',
|
studentIdentifier: 'L.C',
|
||||||
goals: [
|
goals: [
|
||||||
{ goalId: 'g15', goalParentId: null, title: 'Improve typing speed', description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', progressEventCount: 3, benchmarkCount: 0 },
|
{ goalId: 'g15', goalParentId: null, description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', baseline: '', progressEventCount: 3, benchmarkCount: 0 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -116,9 +116,9 @@ export class DummyStudentService {
|
|||||||
const newGoal: StudentGoalItem = {
|
const newGoal: StudentGoalItem = {
|
||||||
goalId: `g${Date.now()}`,
|
goalId: `g${Date.now()}`,
|
||||||
goalParentId: null,
|
goalParentId: null,
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
description: data.description,
|
||||||
category: data.category,
|
category: data.category,
|
||||||
|
baseline: data.baseline,
|
||||||
progressEventCount: 0,
|
progressEventCount: 0,
|
||||||
benchmarkCount: 0,
|
benchmarkCount: 0,
|
||||||
};
|
};
|
||||||
@@ -174,9 +174,9 @@ export class DummyStudentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const benchmarks: BenchmarkDto[] = [
|
const benchmarks: BenchmarkDto[] = [
|
||||||
{ benchmarkId: 'bm1', goalId: 'g1', goalTitle: 'Improve reading comprehension', benchmark: 'Student will identify the main idea of a grade-level nonfiction passage with 80% accuracy.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-15'), updatedAt: null },
|
{ benchmarkId: 'bm1', goalId: 'g1', goalCategory: 'Academics', benchmark: 'Student will identify the main idea of a grade-level nonfiction passage with 80% accuracy.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-15'), updatedAt: null },
|
||||||
{ benchmarkId: 'bm2', goalId: 'g1', goalTitle: 'Improve reading comprehension', benchmark: 'Student will make at least two supported inferences per reading session.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-16'), updatedAt: new Date('2026-02-20') },
|
{ benchmarkId: 'bm2', goalId: 'g1', goalCategory: 'Academics', benchmark: 'Student will make at least two supported inferences per reading session.', createdByName: 'Jane Smith', createdAt: new Date('2026-02-16'), updatedAt: new Date('2026-02-20') },
|
||||||
{ benchmarkId: 'bm3', goalId: 'g3', goalTitle: 'Weekly journal entries', benchmark: 'Student will complete a minimum of one paragraph (5 sentences) per journal entry.', createdByName: 'John Doe', createdAt: new Date('2026-02-18'), updatedAt: null },
|
{ benchmarkId: 'bm3', goalId: 'g3', goalCategory: 'Communication', benchmark: 'Student will complete a minimum of one paragraph (5 sentences) per journal entry.', createdByName: 'John Doe', createdAt: new Date('2026-02-18'), updatedAt: null },
|
||||||
];
|
];
|
||||||
|
|
||||||
return ApiResult.ok({
|
return ApiResult.ok({
|
||||||
|
|||||||
@@ -216,9 +216,9 @@ export class StudentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
// Updates a goal's title, description, and category.
|
// Updates a goal's description, category, and baseline.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
async updateGoal(studentId: string, goalId: string, data: { title?: string; description?: string; category?: string }): Promise<ApiResult<any>> {
|
async updateGoal(studentId: string, goalId: string, data: { description?: string; category?: string; baseline?: string }): Promise<ApiResult<any>> {
|
||||||
try {
|
try {
|
||||||
const result = await firstValueFrom(
|
const result = await firstValueFrom(
|
||||||
this.http.put<ResponseResult<any>>(`${this.base}/api/Student/${studentId}/goals/${goalId}`, data)
|
this.http.put<ResponseResult<any>>(`${this.base}/api/Student/${studentId}/goals/${goalId}`, data)
|
||||||
|
|||||||
Reference in New Issue
Block a user