mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 04:07:39 +00:00
lots of work done
This commit is contained in:
@@ -97,11 +97,85 @@ public class StudentController : BaseController
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("{idStudent:guid}/goals")]
|
||||||
|
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult<StudentGoalSummary>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult<StudentGoalSummary>), StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<ResponseResult<StudentGoalSummary>>> GetGoals(Guid idStudent)
|
||||||
|
{
|
||||||
|
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<StudentGoalSummary>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Student not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var summary = await _studentRepository.GetGoalSummaryAsync(idStudent);
|
||||||
|
|
||||||
|
return Ok(new ResponseResult<StudentGoalSummary>
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "Goals retrieved successfully.",
|
||||||
|
Data = summary
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{idStudent:guid}/progress-event")]
|
||||||
|
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult), StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(typeof(ResponseResult), StatusCodes.Status400BadRequest)]
|
||||||
|
public async Task<ActionResult<ResponseResult>> AddProgressEvent(Guid idStudent, [FromBody] AddProgressEventDto 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."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var created = await _studentRepository.AddProgressEventAsync(userId, dto);
|
||||||
|
if (!created)
|
||||||
|
{
|
||||||
|
return BadRequest(new ResponseResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Unable to add progress event."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return StatusCode(StatusCodes.Status201Created, new ResponseResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "Progress event added successfully."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Roles = $"{UserRoles.Teacher}")]
|
[Authorize(Roles = $"{UserRoles.Teacher}")]
|
||||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status201Created)]
|
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status201Created)]
|
||||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status400BadRequest)]
|
||||||
public async Task<ActionResult<ResponseResult<StudentResponse>>> Create([FromBody] CreateStudentDto newStudentData)
|
public async Task<ActionResult<ResponseResult<StudentResponse>>> CreateStudent([FromBody] CreateStudentDto newStudentData)
|
||||||
{
|
{
|
||||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace WinStudentGoalTracker.DataAccess;
|
||||||
|
|
||||||
|
public class AddProgressEventDto
|
||||||
|
{
|
||||||
|
public Guid GoalId { get; set; }
|
||||||
|
public string? Content { get; set; }
|
||||||
|
public bool IsSensitive { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace WinStudentGoalTracker.DataAccess;
|
||||||
|
|
||||||
|
public class dbStudentGoalRow
|
||||||
|
{
|
||||||
|
public string? StudentIdentifier { get; set; }
|
||||||
|
public required Guid GoalId { get; set; }
|
||||||
|
public Guid? GoalParentId { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? Category { get; set; }
|
||||||
|
public int ProgressEventCount { get; set; }
|
||||||
|
}
|
||||||
@@ -83,4 +83,48 @@ public class StudentRepository
|
|||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AddProgressEventAsync(Guid userId, AddProgressEventDto dto)
|
||||||
|
{
|
||||||
|
using var db = Connection;
|
||||||
|
var rowsAffected = await db.ExecuteAsync(
|
||||||
|
"sp_ProgressEvent_Insert",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
p_id_progress_event = Guid.NewGuid().ToString(),
|
||||||
|
p_id_goal = dto.GoalId.ToString(),
|
||||||
|
p_id_user_created = userId.ToString(),
|
||||||
|
p_content = dto.Content,
|
||||||
|
p_is_sensitive = dto.IsSensitive ? 1 : 0
|
||||||
|
},
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StudentGoalSummary?> GetGoalSummaryAsync(Guid idStudent)
|
||||||
|
{
|
||||||
|
using var db = Connection;
|
||||||
|
var rows = await db.QueryAsync<dbStudentGoalRow>(
|
||||||
|
"sp_Goal_GetByStudentId",
|
||||||
|
new { p_id_student = idStudent.ToString() },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
var list = rows.ToList();
|
||||||
|
if (list.Count == 0) return null;
|
||||||
|
|
||||||
|
return new StudentGoalSummary
|
||||||
|
{
|
||||||
|
StudentIdentifier = list[0].StudentIdentifier,
|
||||||
|
Goals = list.Select(r => new StudentGoalItem
|
||||||
|
{
|
||||||
|
GoalId = r.GoalId,
|
||||||
|
GoalParentId = r.GoalParentId,
|
||||||
|
Title = r.Title,
|
||||||
|
Description = r.Description,
|
||||||
|
Category = r.Category,
|
||||||
|
ProgressEventCount = r.ProgressEventCount
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace WinStudentGoalTracker.Models;
|
||||||
|
|
||||||
|
public class StudentGoalItem
|
||||||
|
{
|
||||||
|
public Guid GoalId { get; set; }
|
||||||
|
public Guid? GoalParentId { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? Category { get; set; }
|
||||||
|
public int ProgressEventCount { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace WinStudentGoalTracker.Models;
|
||||||
|
|
||||||
|
public class StudentGoalSummary
|
||||||
|
{
|
||||||
|
public string? StudentIdentifier { get; set; }
|
||||||
|
public List<StudentGoalItem> Goals { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
DELIMITER ;;
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `sp_Goal_GetByStudentId`(IN p_id_student CHAR(36))
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
s.`identifier` AS `studentIdentifier`,
|
||||||
|
vc.`goalId`,
|
||||||
|
vc.`goalParentId`,
|
||||||
|
vc.`title`,
|
||||||
|
vc.`description`,
|
||||||
|
vc.`category`,
|
||||||
|
vc.`progressEventCount`
|
||||||
|
FROM `v_goal_card` vc
|
||||||
|
INNER JOIN `student` s ON s.`id_student` = vc.`studentId`
|
||||||
|
WHERE vc.`studentId` = p_id_student
|
||||||
|
ORDER BY vc.`goalId`;
|
||||||
|
END;;
|
||||||
|
DELIMITER ;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE OR REPLACE VIEW `v_goal_card` AS
|
||||||
|
SELECT
|
||||||
|
goal.`id_goal` AS `goalId`,
|
||||||
|
goal.`id_goal_parent` AS `goalParentId`,
|
||||||
|
goal.`id_student` AS `studentId`,
|
||||||
|
goal.`title` AS `title`,
|
||||||
|
goal.`description` AS `description`,
|
||||||
|
goal.`category` AS `category`,
|
||||||
|
COUNT(pe.`id_progress_event`) AS `progressEventCount`
|
||||||
|
FROM `goal`
|
||||||
|
LEFT JOIN `progress_event` pe ON pe.`id_goal` = goal.`id_goal`
|
||||||
|
GROUP BY
|
||||||
|
goal.`id_goal`,
|
||||||
|
goal.`id_goal_parent`,
|
||||||
|
goal.`id_student`,
|
||||||
|
goal.`title`,
|
||||||
|
goal.`description`,
|
||||||
|
goal.`category`;
|
||||||
@@ -10,7 +10,7 @@ FROM `student` s
|
|||||||
LEFT JOIN `goal` g
|
LEFT JOIN `goal` g
|
||||||
ON g.`id_student` = s.`id_student`
|
ON g.`id_student` = s.`id_student`
|
||||||
LEFT JOIN `progress_event` pe
|
LEFT JOIN `progress_event` pe
|
||||||
ON pe.`id_student` = s.`id_student`
|
ON pe.`id_goal` = g.`id_goal`
|
||||||
GROUP BY
|
GROUP BY
|
||||||
s.`id_student`,
|
s.`id_student`,
|
||||||
s.`identifier`,
|
s.`identifier`,
|
||||||
|
|||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
<div class="overlay" (click)="onCancel()"></div>
|
||||||
|
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">Add Student</h2>
|
||||||
|
<button class="close-btn" (click)="onCancel()" aria-label="Close">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="modal-body" (ngSubmit)="onSubmit()" #studentForm="ngForm">
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="identifier">Identifier</label>
|
||||||
|
<input
|
||||||
|
id="identifier"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="form.identifier"
|
||||||
|
name="identifier"
|
||||||
|
required
|
||||||
|
placeholder="e.g. Student123"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="programYear">Program Year</label>
|
||||||
|
<input
|
||||||
|
id="programYear"
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="form.programYear"
|
||||||
|
name="programYear"
|
||||||
|
placeholder="e.g. 2025"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="enrollmentDate">Enrollment Date</label>
|
||||||
|
<input
|
||||||
|
id="enrollmentDate"
|
||||||
|
type="date"
|
||||||
|
[(ngModel)]="form.enrollmentDate"
|
||||||
|
name="enrollmentDate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="expectedGrad">Expected Graduation</label>
|
||||||
|
<input
|
||||||
|
id="expectedGrad"
|
||||||
|
type="date"
|
||||||
|
[(ngModel)]="form.expectedGrad"
|
||||||
|
name="expectedGrad"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (errorMessage()) {
|
||||||
|
<p class="error">{{ errorMessage() }}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn btn-secondary" (click)="onCancel()">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary" [disabled]="studentForm.invalid || isSubmitting()">
|
||||||
|
{{ isSubmitting() ? 'Saving...' : 'Add Student' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
:host {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
||||||
|
width: 420px;
|
||||||
|
max-width: 95vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.25rem 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 1.25rem 1.5rem 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input:focus {
|
||||||
|
border-color: #4f46e5;
|
||||||
|
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #dc2626;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.5rem 1.125rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
border-color: #ddd;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #4f46e5;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #4f46e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background: #4338ca;
|
||||||
|
border-color: #4338ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
import { Component, inject, output, signal } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { CreateStudentDto } from '../../../shared/classes/create-student.dto';
|
||||||
|
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
||||||
|
import { StudentService } from '../../../shared/services/student.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-student-modal',
|
||||||
|
imports: [FormsModule],
|
||||||
|
templateUrl: './add-student-modal.html',
|
||||||
|
styleUrl: './add-student-modal.scss',
|
||||||
|
})
|
||||||
|
export class AddStudentModal {
|
||||||
|
|
||||||
|
// ************************** Constructor **************************
|
||||||
|
|
||||||
|
// ************************** Declarations *************************
|
||||||
|
|
||||||
|
private readonly studentService = inject(StudentService);
|
||||||
|
|
||||||
|
readonly studentCreated = output<StudentCardDto>();
|
||||||
|
readonly cancelled = output<void>();
|
||||||
|
|
||||||
|
protected readonly isSubmitting = signal(false);
|
||||||
|
protected readonly errorMessage = signal<string | null>(null);
|
||||||
|
|
||||||
|
protected form: CreateStudentDto = {
|
||||||
|
identifier: '',
|
||||||
|
programYear: null,
|
||||||
|
enrollmentDate: null,
|
||||||
|
expectedGrad: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ************************** Properties ***************************
|
||||||
|
|
||||||
|
// ************************ Public Methods *************************
|
||||||
|
|
||||||
|
// ************************ Event Handlers *************************
|
||||||
|
|
||||||
|
async onSubmit() {
|
||||||
|
this.errorMessage.set(null);
|
||||||
|
this.isSubmitting.set(true);
|
||||||
|
|
||||||
|
const result = await this.studentService.createStudent(this.form);
|
||||||
|
|
||||||
|
this.isSubmitting.set(false);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
this.errorMessage.set(result.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.studentCreated.emit(result.payload!);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel() {
|
||||||
|
this.cancelled.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************** Support Procedures ***********************
|
||||||
|
}
|
||||||
+7
@@ -2,6 +2,13 @@
|
|||||||
<button class="toolbar-btn" (click)="onAddStudent()">+ Add a Student</button>
|
<button class="toolbar-btn" (click)="onAddStudent()">+ Add a Student</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (showAddModal()) {
|
||||||
|
<app-add-student-modal
|
||||||
|
(studentCreated)="onStudentCreated($event)"
|
||||||
|
(cancelled)="onModalCancelled()"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
@if (displayMode() === 'card') {
|
@if (displayMode() === 'card') {
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
@for (student of students(); track student.studentId) {
|
@for (student of students(); track student.studentId) {
|
||||||
|
|||||||
+16
-4
@@ -1,13 +1,15 @@
|
|||||||
import { Component, inject, signal } from '@angular/core';
|
import { Component, inject, signal } from '@angular/core';
|
||||||
import { StudentCard } from '../student-card/student-card';
|
import { StudentCard } from '../student-card/student-card';
|
||||||
import { StudentService } from '../../../shared/services/dummy-student.service';
|
import { AddStudentModal } from '../add-student-modal/add-student-modal';
|
||||||
|
import { DummyStudentService } from '../../../shared/services/dummy-student.service';
|
||||||
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
||||||
|
import { StudentService } from '../../../shared/services/student.service';
|
||||||
|
|
||||||
export type DisplayMode = 'card' | 'list';
|
export type DisplayMode = 'card' | 'list';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-student-card-list',
|
selector: 'app-student-card-list',
|
||||||
imports: [StudentCard],
|
imports: [StudentCard, AddStudentModal],
|
||||||
templateUrl: './student-card-list.html',
|
templateUrl: './student-card-list.html',
|
||||||
styleUrl: './student-card-list.scss',
|
styleUrl: './student-card-list.scss',
|
||||||
})
|
})
|
||||||
@@ -24,6 +26,7 @@ export class StudentCardList {
|
|||||||
private readonly studentService = inject(StudentService);
|
private readonly studentService = inject(StudentService);
|
||||||
protected readonly students = signal<StudentCardDto[]>([]);
|
protected readonly students = signal<StudentCardDto[]>([]);
|
||||||
protected readonly displayMode = signal<DisplayMode>('card');
|
protected readonly displayMode = signal<DisplayMode>('card');
|
||||||
|
protected readonly showAddModal = signal(false);
|
||||||
|
|
||||||
public errorMessage = signal<String | null>(null);
|
public errorMessage = signal<String | null>(null);
|
||||||
|
|
||||||
@@ -34,7 +37,16 @@ export class StudentCardList {
|
|||||||
// ************************ Event Handlers *************************
|
// ************************ Event Handlers *************************
|
||||||
|
|
||||||
onAddStudent() {
|
onAddStudent() {
|
||||||
// TODO: navigate to add-student form
|
this.showAddModal.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStudentCreated(student: StudentCardDto) {
|
||||||
|
this.students.update(list => [...list, student]);
|
||||||
|
this.showAddModal.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onModalCancelled() {
|
||||||
|
this.showAddModal.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********************** Support Procedures ***********************
|
// ********************** Support Procedures ***********************
|
||||||
@@ -43,7 +55,7 @@ export class StudentCardList {
|
|||||||
// Loads students from the service and populates the students signal.
|
// Loads students from the service and populates the students signal.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
private loadStudents() {
|
private loadStudents() {
|
||||||
this.studentService.getStudentCards().then(data => {
|
this.studentService.getMyStudents().then(data => {
|
||||||
|
|
||||||
if(!data.success)
|
if(!data.success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<h2 class="identifier">🎓 {{ student().identifier }}</h2>
|
<h2 class="identifier">🎓 {{ student().identifier }}</h2>
|
||||||
|
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="badge">Grad Date: {{ student().expectedGradDate }}</span>
|
<span class="last-entry">Grad Date: {{ student().expectedGradDate | date:'M/d/yy'}}</span>
|
||||||
<span class="last-entry">
|
<span class="last-entry">
|
||||||
@if (student().lastEntryDate) {
|
@if (student().lastEntryDate) {
|
||||||
Last entry: {{ student().lastEntryDate | date:'M/d/yy' }}
|
Last entry: {{ student().lastEntryDate | date:'M/d/yy' }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, input } from '@angular/core';
|
import { Component, computed, input } from '@angular/core';
|
||||||
import { DatePipe } from '@angular/common';
|
import { DatePipe } from '@angular/common';
|
||||||
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -2,8 +2,9 @@ import { Component, computed, inject, signal } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { DummySaveProgressEvent } from '../../../shared/services/dummy-save-progress-event.service';
|
|
||||||
import { describeHttpError } from '../../../shared/classes/http-errors';
|
import { describeHttpError } from '../../../shared/classes/http-errors';
|
||||||
|
import { DummyStudentService } from '../../../shared/services/dummy-student.service';
|
||||||
|
import { StudentService } from '../../../shared/services/student.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-progress-event',
|
selector: 'app-add-progress-event',
|
||||||
@@ -26,7 +27,7 @@ export class AddProgressEvent {
|
|||||||
|
|
||||||
private readonly route = inject(ActivatedRoute);
|
private readonly route = inject(ActivatedRoute);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly saveService = inject(DummySaveProgressEvent);
|
private readonly studentService = inject(StudentService);
|
||||||
|
|
||||||
private readonly studentId: string;
|
private readonly studentId: string;
|
||||||
private readonly goalId: string;
|
private readonly goalId: string;
|
||||||
@@ -66,7 +67,7 @@ export class AddProgressEvent {
|
|||||||
this.saving.set(true);
|
this.saving.set(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.saveService.save(this.studentId, this.goalId, this.notes().trim());
|
const result = await this.studentService.addProgressEvent(this.studentId, this.goalId, this.notes().trim());
|
||||||
this.saving.set(false);
|
this.saving.set(false);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.router.navigate(['students', this.studentId, 'goals']);
|
this.router.navigate(['students', this.studentId, 'goals']);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Component, inject, signal } from '@angular/core';
|
import { Component, inject, signal } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { DummyStudentGoalService } from '../../../shared/services/dummy-student-goal.service';
|
|
||||||
import { StudentGoalSummary } from '../../../shared/classes/student-goal';
|
import { StudentGoalSummary } from '../../../shared/classes/student-goal';
|
||||||
|
import { DummyStudentService } from '../../../shared/services/dummy-student.service';
|
||||||
|
import { StudentService } from '../../../shared/services/student.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-student-goals',
|
selector: 'app-student-goals',
|
||||||
@@ -21,7 +22,7 @@ export class StudentGoals {
|
|||||||
|
|
||||||
private readonly route = inject(ActivatedRoute);
|
private readonly route = inject(ActivatedRoute);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly goalService = inject(DummyStudentGoalService);
|
private readonly studentService = inject(StudentService);
|
||||||
|
|
||||||
private readonly studentId = this.route.snapshot.paramMap.get('studentId') ?? '';
|
private readonly studentId = this.route.snapshot.paramMap.get('studentId') ?? '';
|
||||||
protected readonly data = signal<StudentGoalSummary | null>(null);
|
protected readonly data = signal<StudentGoalSummary | null>(null);
|
||||||
@@ -60,7 +61,7 @@ export class StudentGoals {
|
|||||||
private loadGoals() {
|
private loadGoals() {
|
||||||
if (!this.studentId) return;
|
if (!this.studentId) return;
|
||||||
|
|
||||||
this.goalService.getGoalsForStudent(this.studentId).then(result => {
|
this.studentService.getGoalsForStudent(this.studentId).then(result => {
|
||||||
|
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Component, inject, signal } from '@angular/core';
|
import { Component, inject, signal } from '@angular/core';
|
||||||
import { StudentCard } from '../../components/student-card/student-card';
|
import { StudentCard } from '../../components/student-card/student-card';
|
||||||
import { StudentService } from '../../../shared/services/dummy-student.service';
|
import { DummyStudentService } from '../../../shared/services/dummy-student.service';
|
||||||
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
import { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
||||||
|
import { StudentService } from '../../../shared/services/student.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-students',
|
selector: 'app-students',
|
||||||
@@ -36,7 +37,7 @@ export class Students {
|
|||||||
// Loads the list of students assigned to the current user.
|
// Loads the list of students assigned to the current user.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
private loadStudents() {
|
private loadStudents() {
|
||||||
this.studentService.getStudentsForUser().then(data => {
|
this.studentService.getMyStudents().then(data => {
|
||||||
|
|
||||||
if (!data.success)
|
if (!data.success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface CreateStudentDto {
|
||||||
|
identifier: string;
|
||||||
|
programYear: number | null;
|
||||||
|
enrollmentDate: Date | null;
|
||||||
|
expectedGrad: Date | null;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ 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;
|
||||||
title: string; // goal.title — varchar(255)
|
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)
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable, of } from 'rxjs';
|
|
||||||
import { ApiResult } from '../classes/api-result';
|
|
||||||
|
|
||||||
// *****************************************************************
|
|
||||||
// TODO: This dummy service should be replaced by SaveProgressEvent,
|
|
||||||
// which will POST real data to the API.
|
|
||||||
// *****************************************************************
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class DummySaveProgressEvent {
|
|
||||||
|
|
||||||
// ************************** Constructor **************************
|
|
||||||
|
|
||||||
// ************************** Declarations *************************
|
|
||||||
|
|
||||||
// ************************** Properties ***************************
|
|
||||||
|
|
||||||
// ************************ Public Methods *************************
|
|
||||||
|
|
||||||
// *****************************************************************
|
|
||||||
// TODO: DUMMY — Always returns success. Replace with
|
|
||||||
// SaveProgressEvent calling POST /api/progress-events
|
|
||||||
// *****************************************************************
|
|
||||||
async save(studentId: string, goalId: string, content: string): Promise<ApiResult> {
|
|
||||||
return ApiResult.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************************ Event Handlers *************************
|
|
||||||
|
|
||||||
// ********************** Support Procedures ***********************
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ApiResult } from '../classes/api-result';
|
|
||||||
import { StudentGoalSummary } from '../classes/student-goal';
|
|
||||||
|
|
||||||
// *****************************************************************
|
|
||||||
// TODO: This dummy service should be replaced by StudentGoalService,
|
|
||||||
// which will fetch real data from the API.
|
|
||||||
// *****************************************************************
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class DummyStudentGoalService {
|
|
||||||
|
|
||||||
// ************************** Constructor **************************
|
|
||||||
|
|
||||||
// ************************** Declarations *************************
|
|
||||||
|
|
||||||
// *****************************************************************
|
|
||||||
// TODO: DUMMY DATA — Maps studentId to identifier and goals.
|
|
||||||
// Replace with StudentGoalService calling
|
|
||||||
// GET /api/students/:id/goals
|
|
||||||
// *****************************************************************
|
|
||||||
private readonly data: Record<string, StudentGoalSummary> = {
|
|
||||||
'1': {
|
|
||||||
studentIdentifier: 'J.B',
|
|
||||||
goals: [
|
|
||||||
{ goalId: 'g1', title: 'Improve reading comprehension', description: 'Work on main-idea identification and inference skills across fiction and nonfiction texts.', category: 'Academics', progressEventCount: 5 },
|
|
||||||
{ goalId: 'g2', title: 'Complete algebra module', description: 'Finish all units in the algebra course including linear equations and graphing.', category: 'Academics', progressEventCount: 2 },
|
|
||||||
{ goalId: 'g3', title: 'Weekly journal entries', description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', progressEventCount: 8 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
studentIdentifier: 'M.K',
|
|
||||||
goals: [
|
|
||||||
{ goalId: 'g4', title: 'Pass certification exam', description: 'Prepare for and pass the industry certification exam by end of quarter.', category: 'Career Readiness', progressEventCount: 3 },
|
|
||||||
{ goalId: 'g5', title: 'Attendance above 90%', description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', progressEventCount: 0 },
|
|
||||||
{ goalId: 'g6', title: 'Complete internship hours', description: 'Log the required 40 hours at the assigned internship site.', category: 'Career Readiness', progressEventCount: 12 },
|
|
||||||
{ goalId: 'g7', title: 'Portfolio project', description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', progressEventCount: 1 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
studentIdentifier: 'A.R',
|
|
||||||
goals: [
|
|
||||||
{ goalId: 'g8', title: 'GED preparation', description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', progressEventCount: 6 },
|
|
||||||
{ goalId: 'g9', title: 'Resume workshop', description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', progressEventCount: 0 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'4': {
|
|
||||||
studentIdentifier: 'T.W',
|
|
||||||
goals: [
|
|
||||||
{ goalId: 'g10', title: 'Public speaking practice', description: 'Present in front of the class at least once per month.', category: 'Communication', progressEventCount: 4 },
|
|
||||||
{ goalId: 'g11', title: 'Math placement improvement', description: 'Move up one placement level in math by the end of the semester.', category: 'Academics', progressEventCount: 7 },
|
|
||||||
{ goalId: 'g12', title: 'Conflict resolution strategies', description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', progressEventCount: 2 },
|
|
||||||
{ goalId: 'g13', title: 'Daily attendance streak', description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', progressEventCount: 0 },
|
|
||||||
{ goalId: 'g14', title: 'Job shadow experience', description: 'Complete a job shadow day in a field of interest.', category: 'Career Readiness', progressEventCount: 1 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'5': {
|
|
||||||
studentIdentifier: 'L.C',
|
|
||||||
goals: [
|
|
||||||
{ goalId: 'g15', title: 'Improve typing speed', description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', progressEventCount: 3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************************** Properties ***************************
|
|
||||||
|
|
||||||
// ************************ Public Methods *************************
|
|
||||||
|
|
||||||
// *****************************************************************
|
|
||||||
// Returns the student's identifier and their list of goals,
|
|
||||||
// given a student ID.
|
|
||||||
// *****************************************************************
|
|
||||||
async getGoalsForStudent(studentId: string): Promise<ApiResult<StudentGoalSummary | null>> {
|
|
||||||
var goals = this.data[studentId] ?? null;
|
|
||||||
if (goals === null)
|
|
||||||
{
|
|
||||||
return ApiResult.fail('Student not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResult.ok(goals);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************************ Event Handlers *************************
|
|
||||||
|
|
||||||
// ********************** Support Procedures ***********************
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,60 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, of } from 'rxjs';
|
|
||||||
import { StudentCardDto } from '../classes/student-card.dto';
|
import { StudentCardDto } from '../classes/student-card.dto';
|
||||||
import { ApiResult } from '../classes/api-result';
|
import { ApiResult } from '../classes/api-result';
|
||||||
|
import { StudentGoalSummary } from '../classes/student-goal';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class StudentService {
|
export class DummyStudentService {
|
||||||
|
|
||||||
// ************************** Constructor **************************
|
// ************************** Constructor **************************
|
||||||
|
|
||||||
// ************************** Declarations *************************
|
// ************************** Declarations *************************
|
||||||
|
|
||||||
|
private readonly data: Record<string, StudentGoalSummary> = {
|
||||||
|
'1': {
|
||||||
|
studentIdentifier: 'J.B',
|
||||||
|
goals: [
|
||||||
|
{ goalId: 'g1', goalParentId: null, title: 'Improve reading comprehension', description: 'Work on main-idea identification and inference skills across fiction and nonfiction texts.', category: 'Academics', progressEventCount: 5 },
|
||||||
|
{ goalId: 'g2', goalParentId: null, title: 'Complete algebra module', description: 'Finish all units in the algebra course including linear equations and graphing.', category: 'Academics', progressEventCount: 2 },
|
||||||
|
{ goalId: 'g3', goalParentId: null, title: 'Weekly journal entries', description: 'Write a reflective journal entry each week to build writing fluency.', category: 'Communication', progressEventCount: 8 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
studentIdentifier: 'M.K',
|
||||||
|
goals: [
|
||||||
|
{ goalId: 'g4', goalParentId: null, title: 'Pass certification exam', description: 'Prepare for and pass the industry certification exam by end of quarter.', category: 'Career Readiness', progressEventCount: 3 },
|
||||||
|
{ goalId: 'g5', goalParentId: null, title: 'Attendance above 90%', description: 'Maintain consistent attendance throughout the term.', category: 'Behavior', progressEventCount: 0 },
|
||||||
|
{ goalId: 'g6', goalParentId: null, title: 'Complete internship hours', description: 'Log the required 40 hours at the assigned internship site.', category: 'Career Readiness', progressEventCount: 12 },
|
||||||
|
{ goalId: 'g7', goalParentId: null, title: 'Portfolio project', description: 'Build a personal portfolio showcasing completed coursework and projects.', category: 'Career Readiness', progressEventCount: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
studentIdentifier: 'A.R',
|
||||||
|
goals: [
|
||||||
|
{ goalId: 'g8', goalParentId: null, title: 'GED preparation', description: 'Complete practice tests and study modules for GED math and reading sections.', category: 'Academics', progressEventCount: 6 },
|
||||||
|
{ goalId: 'g9', goalParentId: null, title: 'Resume workshop', description: 'Attend the resume writing workshop and produce a final draft.', category: 'Career Readiness', progressEventCount: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
studentIdentifier: 'T.W',
|
||||||
|
goals: [
|
||||||
|
{ goalId: 'g10', goalParentId: null, title: 'Public speaking practice', description: 'Present in front of the class at least once per month.', category: 'Communication', progressEventCount: 4 },
|
||||||
|
{ goalId: 'g11', goalParentId: null, title: 'Math placement improvement', description: 'Move up one placement level in math by the end of the semester.', category: 'Academics', progressEventCount: 7 },
|
||||||
|
{ goalId: 'g12', goalParentId: null, title: 'Conflict resolution strategies', description: 'Learn and apply at least three de-escalation techniques.', category: 'Behavior', progressEventCount: 2 },
|
||||||
|
{ goalId: 'g13', goalParentId: null, title: 'Daily attendance streak', description: 'Achieve a 30-day unbroken attendance streak.', category: 'Behavior', progressEventCount: 0 },
|
||||||
|
{ goalId: 'g14', goalParentId: null, title: 'Job shadow experience', description: 'Complete a job shadow day in a field of interest.', category: 'Career Readiness', progressEventCount: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'5': {
|
||||||
|
studentIdentifier: 'L.C',
|
||||||
|
goals: [
|
||||||
|
{ goalId: 'g15', goalParentId: null, title: 'Improve typing speed', description: 'Reach 40 WPM with 95% accuracy on typing assessments.', category: 'Career Readiness', progressEventCount: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ************************** Properties ***************************
|
// ************************** Properties ***************************
|
||||||
|
|
||||||
// ************************ Public Methods *************************
|
// ************************ Public Methods *************************
|
||||||
@@ -20,7 +63,7 @@ export class StudentService {
|
|||||||
// Returns student card summaries. Currently returns dummy data
|
// Returns student card summaries. Currently returns dummy data
|
||||||
// until the API endpoint is available.
|
// until the API endpoint is available.
|
||||||
// *****************************************************************
|
// *****************************************************************
|
||||||
async getStudentCards(): Promise<ApiResult<StudentCardDto[]>> {
|
async getMyStudents(): Promise<ApiResult<StudentCardDto[]>> {
|
||||||
var payload = [
|
var payload = [
|
||||||
{
|
{
|
||||||
studentId: '1',
|
studentId: '1',
|
||||||
@@ -51,24 +94,22 @@ export class StudentService {
|
|||||||
return ApiResult.ok(payload);
|
return ApiResult.ok(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************
|
async getGoalsForStudent(studentId: string): Promise<ApiResult<StudentGoalSummary | null>> {
|
||||||
// TODO: DUMMY DATA — Replace with getStudentsPerUser, which will
|
var goals = this.data[studentId] ?? null;
|
||||||
// call GET /api/users/:id/students to return real data.
|
if (goals === null)
|
||||||
// Returns students assigned to the current user with their
|
{
|
||||||
// identifier, age, goal count, and progress event count.
|
return ApiResult.fail('Student not found');
|
||||||
// *****************************************************************
|
|
||||||
async getStudentsForUser(): Promise<ApiResult<StudentCardDto[]>> {
|
|
||||||
var payload = [
|
|
||||||
{ studentId: '1', identifier: 'J.B', expectedGradDate: new Date('2027-02-27'), lastEntryDate: new Date('2026-02-21'), goalCount: 3, progressEventCount: 5 },
|
|
||||||
{ studentId: '2', identifier: 'M.K', expectedGradDate: new Date('2027-02-27'), lastEntryDate: new Date('2026-02-25'), goalCount: 4, progressEventCount: 8 },
|
|
||||||
{ studentId: '3', identifier: 'A.R', expectedGradDate: new Date('2027-02-27'), lastEntryDate: null, goalCount: 2, progressEventCount: 0 },
|
|
||||||
{ studentId: '4', identifier: 'T.W', expectedGradDate: new Date('2027-02-27'), lastEntryDate: new Date('2026-02-18'), goalCount: 5, progressEventCount: 12 },
|
|
||||||
{ studentId: '5', identifier: 'L.C', expectedGradDate: new Date('2027-02-27'), lastEntryDate: new Date('2026-02-27'), goalCount: 1, progressEventCount: 2 },
|
|
||||||
];
|
|
||||||
|
|
||||||
return ApiResult.ok(payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ApiResult.ok(goals);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async addProgressEvent(studentId: string, goalId: string, content: string): Promise<ApiResult> {
|
||||||
|
return ApiResult.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ************************ Event Handlers *************************
|
// ************************ Event Handlers *************************
|
||||||
|
|
||||||
// ********************** Support Procedures ***********************
|
// ********************** Support Procedures ***********************
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { ApiResult } from '../classes/api-result';
|
||||||
|
import { ResponseResult } from '../classes/auth.models';
|
||||||
|
import { describeHttpError } from '../classes/http-errors';
|
||||||
|
import { CreateStudentDto } from '../classes/create-student.dto';
|
||||||
|
import { StudentCardDto } from '../classes/student-card.dto';
|
||||||
|
import { StudentGoalSummary } from '../classes/student-goal';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class StudentService {
|
||||||
|
|
||||||
|
// ************************** Constructor **************************
|
||||||
|
|
||||||
|
// ************************** Declarations *************************
|
||||||
|
|
||||||
|
private readonly http = inject(HttpClient);
|
||||||
|
private readonly base = environment.apiBaseUrl;
|
||||||
|
|
||||||
|
// ************************** Properties ***************************
|
||||||
|
|
||||||
|
// ************************ Public Methods *************************
|
||||||
|
|
||||||
|
// *****************************************************************
|
||||||
|
// Returns student card summaries for the authenticated user.
|
||||||
|
// *****************************************************************
|
||||||
|
async getMyStudents(): Promise<ApiResult<StudentCardDto[]>> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.get<ResponseResult<StudentCardDto[]>>(`${this.base}/api/Student/my`)
|
||||||
|
);
|
||||||
|
return result.success && result.data
|
||||||
|
? ApiResult.ok(result.data)
|
||||||
|
: ApiResult.fail(result.message);
|
||||||
|
} catch (error) {
|
||||||
|
return ApiResult.fail(describeHttpError(error as HttpErrorResponse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************************************************
|
||||||
|
// Returns goal summary for a given student.
|
||||||
|
// *****************************************************************
|
||||||
|
async getGoalsForStudent(studentId: string): Promise<ApiResult<StudentGoalSummary | null>> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.get<ResponseResult<StudentGoalSummary>>(`${this.base}/api/Student/${studentId}/goals`)
|
||||||
|
);
|
||||||
|
return result.success && result.data
|
||||||
|
? ApiResult.ok(result.data)
|
||||||
|
: ApiResult.fail(result.message);
|
||||||
|
} catch (error) {
|
||||||
|
return ApiResult.fail(describeHttpError(error as HttpErrorResponse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************************************************
|
||||||
|
// Creates a new student and returns the created student card.
|
||||||
|
// *****************************************************************
|
||||||
|
async createStudent(data: CreateStudentDto): Promise<ApiResult<StudentCardDto>> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.post<ResponseResult<StudentCardDto>>(`${this.base}/api/Student`, data)
|
||||||
|
);
|
||||||
|
return result.success && result.data
|
||||||
|
? ApiResult.ok(result.data)
|
||||||
|
: ApiResult.fail(result.message);
|
||||||
|
} catch (error) {
|
||||||
|
return ApiResult.fail(describeHttpError(error as HttpErrorResponse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addProgressEvent(studentId: string, goalId: string, content: string): Promise<ApiResult> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.post<ResponseResult<void>>(`${this.base}/api/Student/${studentId}/progress-event`, { goalId, content })
|
||||||
|
);
|
||||||
|
return result.success
|
||||||
|
? ApiResult.empty()
|
||||||
|
: ApiResult.fail(result.message);
|
||||||
|
} catch (error) {
|
||||||
|
return ApiResult.fail(describeHttpError(error as HttpErrorResponse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ************************ Event Handlers *************************
|
||||||
|
|
||||||
|
// ********************** Support Procedures ***********************
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user