mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 12:17:35 +00:00
lots of work done
This commit is contained in:
+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>
|
||||
</div>
|
||||
|
||||
@if (showAddModal()) {
|
||||
<app-add-student-modal
|
||||
(studentCreated)="onStudentCreated($event)"
|
||||
(cancelled)="onModalCancelled()"
|
||||
/>
|
||||
}
|
||||
|
||||
@if (displayMode() === 'card') {
|
||||
<div class="card-grid">
|
||||
@for (student of students(); track student.studentId) {
|
||||
|
||||
+16
-4
@@ -1,13 +1,15 @@
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
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 { StudentService } from '../../../shared/services/student.service';
|
||||
|
||||
export type DisplayMode = 'card' | 'list';
|
||||
|
||||
@Component({
|
||||
selector: 'app-student-card-list',
|
||||
imports: [StudentCard],
|
||||
imports: [StudentCard, AddStudentModal],
|
||||
templateUrl: './student-card-list.html',
|
||||
styleUrl: './student-card-list.scss',
|
||||
})
|
||||
@@ -24,6 +26,7 @@ export class StudentCardList {
|
||||
private readonly studentService = inject(StudentService);
|
||||
protected readonly students = signal<StudentCardDto[]>([]);
|
||||
protected readonly displayMode = signal<DisplayMode>('card');
|
||||
protected readonly showAddModal = signal(false);
|
||||
|
||||
public errorMessage = signal<String | null>(null);
|
||||
|
||||
@@ -34,7 +37,16 @@ export class StudentCardList {
|
||||
// ************************ Event Handlers *************************
|
||||
|
||||
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 ***********************
|
||||
@@ -43,7 +55,7 @@ export class StudentCardList {
|
||||
// Loads students from the service and populates the students signal.
|
||||
// *****************************************************************
|
||||
private loadStudents() {
|
||||
this.studentService.getStudentCards().then(data => {
|
||||
this.studentService.getMyStudents().then(data => {
|
||||
|
||||
if(!data.success)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<h2 class="identifier">🎓 {{ student().identifier }}</h2>
|
||||
|
||||
<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">
|
||||
@if (student().lastEntryDate) {
|
||||
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 { StudentCardDto } from '../../../shared/classes/student-card.dto';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user