mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 08:47:42 +00:00
Latest
This commit is contained in:
+11
-4
@@ -11,11 +11,18 @@
|
||||
}
|
||||
|
||||
@if (displayMode() === 'card') {
|
||||
<div class="card-grid">
|
||||
@for (student of students(); track student.studentId) {
|
||||
<app-student-card [student]="student" />
|
||||
@if (loaded() && students().length === 0) {
|
||||
<div class="empty-state">
|
||||
<p>You don't have any students entered yet.</p>
|
||||
<p>Click <strong>+ Add a Student</strong> in the upper right to get started.</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="card-grid">
|
||||
@for (student of students(); track student.studentId) {
|
||||
<app-student-card [student]="student" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<!-- List mode — to be implemented -->
|
||||
<p>List view coming soon.</p>
|
||||
|
||||
+10
@@ -45,4 +45,14 @@
|
||||
gap: 1rem;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 1.5rem;
|
||||
color: #555;
|
||||
font-size: 0.9375rem;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
+2
@@ -27,6 +27,7 @@ export class StudentCardList {
|
||||
protected readonly students = signal<StudentCardDto[]>([]);
|
||||
protected readonly displayMode = signal<DisplayMode>('card');
|
||||
protected readonly showAddModal = signal(false);
|
||||
protected readonly loaded = signal(false);
|
||||
|
||||
public errorMessage = signal<String | null>(null);
|
||||
|
||||
@@ -73,6 +74,7 @@ export class StudentCardList {
|
||||
this.students.set(this.sortByIdentifier(data.payload || []))
|
||||
}
|
||||
|
||||
this.loaded.set(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="menu-toggle" (click)="onToggleSidebar()">☰</button>
|
||||
<span class="header-title">WinStudentGoalTracker</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="header-title">{{ auth.schoolDistrictName() }} — {{ auth.programName() }}</span>
|
||||
<button class="logout-btn" (click)="onLogout()">Log Out</button>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -37,15 +37,13 @@
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background: none;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
@@ -41,7 +41,7 @@ export class Home implements OnDestroy {
|
||||
|
||||
// ************************** Declarations *************************
|
||||
|
||||
private readonly auth = inject(Auth);
|
||||
protected readonly auth = inject(Auth);
|
||||
private readonly router = inject(Router);
|
||||
private readonly studentService = inject(StudentService);
|
||||
private readonly routeSub: Subscription;
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface SelectProgramResponse {
|
||||
userId: string;
|
||||
email: string;
|
||||
programName: string;
|
||||
schoolDistrictName: string;
|
||||
jwt: string;
|
||||
refreshToken: string;
|
||||
role: string;
|
||||
|
||||
@@ -34,6 +34,8 @@ export class Auth {
|
||||
private readonly _sessionToken = signal<string | null>(this.loadSessionToken());
|
||||
private readonly _programs = signal<UserProgramSummary[]>([]);
|
||||
private readonly _isRefreshing = signal(false);
|
||||
private readonly _programName = signal<string>('');
|
||||
private readonly _schoolDistrictName = signal<string>('');
|
||||
|
||||
/** The currently authenticated user, parsed from the JWT. Null when logged out. */
|
||||
readonly user = computed<AuthUser | null>(() => {
|
||||
@@ -50,6 +52,12 @@ export class Auth {
|
||||
/** Programs returned by phase 1 for the user to choose from. */
|
||||
readonly programs = this._programs.asReadonly();
|
||||
|
||||
/** The name of the currently selected program. */
|
||||
readonly programName = this._programName.asReadonly();
|
||||
|
||||
/** The name of the school district for the currently selected program. */
|
||||
readonly schoolDistrictName = this._schoolDistrictName.asReadonly();
|
||||
|
||||
/** Emits when a token refresh fails and the user is forced to re-login. */
|
||||
readonly sessionExpired$ = new Subject<void>();
|
||||
|
||||
@@ -187,6 +195,10 @@ export class Auth {
|
||||
private handleFullAuth(data: SelectProgramResponse): void {
|
||||
this.storeTokens(data.jwt, data.refreshToken);
|
||||
|
||||
// Store program context for header display
|
||||
this._programName.set(data.programName);
|
||||
this._schoolDistrictName.set(data.schoolDistrictName);
|
||||
|
||||
// Clear phase-1 artefacts
|
||||
localStorage.removeItem(STORAGE_KEYS.SESSION_TOKEN);
|
||||
this._sessionToken.set(null);
|
||||
@@ -229,6 +241,8 @@ export class Auth {
|
||||
this._sessionToken.set(null);
|
||||
this._programs.set([]);
|
||||
this._isRefreshing.set(false);
|
||||
this._programName.set('');
|
||||
this._schoolDistrictName.set('');
|
||||
}
|
||||
|
||||
private loadSessionToken(): string | null {
|
||||
|
||||
Reference in New Issue
Block a user