chore(frontend): delete obsolete login and notes routes
This commit is contained in:
@@ -1,172 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { api } from '$lib/api';
|
||||
import RoomCanvas from '$lib/RoomCanvas.svelte';
|
||||
import type { Course, Session, Slot, Room, Student, LayoutElement, Note } from '$lib/types';
|
||||
|
||||
let courses = $state<Course[]>([]);
|
||||
let selectedCourseId = $state<number | null>(null);
|
||||
let sessions = $state<Session[]>([]);
|
||||
let selectedSessionId = $state<number | null>(null);
|
||||
let slots = $state<Slot[]>([]);
|
||||
let selectedSlotId = $state<number | null>(null);
|
||||
|
||||
let layout = $state<LayoutElement[]>([]);
|
||||
let notes = $state<Note[]>([]);
|
||||
let students = $state<Student[]>([]);
|
||||
let attendances = $state<any[]>([]);
|
||||
|
||||
onMount(async () => {
|
||||
courses = await api.admin.courses.list();
|
||||
if (courses.length > 0) selectedCourseId = courses[0].id;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (selectedCourseId) {
|
||||
api.admin.sessions.list(selectedCourseId).then(res => {
|
||||
sessions = res;
|
||||
if (sessions.length > 0) selectedSessionId = sessions[0].id;
|
||||
});
|
||||
api.admin.courses.listStudents(selectedCourseId).then(res => students = res);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (selectedSessionId) {
|
||||
const s = sessions.find(s => s.id === selectedSessionId);
|
||||
slots = s?.slots || [];
|
||||
if (slots.length > 0) selectedSlotId = slots[0].id;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (selectedSlotId) {
|
||||
const slot = slots.find(s => s.id === selectedSlotId);
|
||||
if (slot?.room_id) {
|
||||
api.admin.rooms.get(slot.room_id).then(r => layout = r.layout);
|
||||
} else {
|
||||
layout = [];
|
||||
}
|
||||
api.admin.slots.getNotes(selectedSlotId).then(res => notes = res);
|
||||
api.admin.sessions.getAttendance(selectedSessionId!).then(res => {
|
||||
attendances = res.attendances.filter((a: any) => a.slot_id === selectedSlotId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let activeSeatId = $state<string | null>(null);
|
||||
let noteContent = $state('');
|
||||
|
||||
function handleSeatClick(el: LayoutElement) {
|
||||
if (el.type !== 'seat') return;
|
||||
activeSeatId = el.id;
|
||||
const studentId = attendances.find(a => a.seat_id === el.id)?.student_id;
|
||||
if (studentId) {
|
||||
const existing = notes.find(n => n.student_id === studentId);
|
||||
noteContent = existing?.content || '';
|
||||
} else {
|
||||
noteContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveNote() {
|
||||
if (!selectedSlotId || !activeSeatId) return;
|
||||
const studentId = attendances.find(a => a.seat_id === activeSeatId)?.student_id;
|
||||
if (!studentId) {
|
||||
alert('No student at this seat');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.admin.slots.upsertNote(selectedSlotId, studentId, noteContent);
|
||||
notes = await api.admin.slots.getNotes(selectedSlotId);
|
||||
activeSeatId = null;
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
}
|
||||
|
||||
let studentNames = $derived.by(() => {
|
||||
const map: Record<string, string> = {};
|
||||
attendances.forEach(a => {
|
||||
if (a.seat_id) {
|
||||
const s = students.find(s => s.id === a.student_id);
|
||||
map[a.seat_id] = s?.name || 'Unknown';
|
||||
}
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
let occupiedSeatIds = $derived(attendances.map(a => a.seat_id).filter(id => id !== null));
|
||||
</script>
|
||||
|
||||
<h1>Seat Notes</h1>
|
||||
|
||||
<div class="selectors">
|
||||
<select bind:value={selectedCourseId}>
|
||||
{#each courses as course}
|
||||
<option value={course.id}>{course.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<select bind:value={selectedSessionId}>
|
||||
{#each sessions as session}
|
||||
<option value={session.id}>Week {session.week_nr}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<select bind:value={selectedSlotId}>
|
||||
{#each slots as slot}
|
||||
<option value={slot.id}>{slot.start_time} - {slot.end_time}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="notes-container">
|
||||
<div class="map-view">
|
||||
<RoomCanvas
|
||||
elements={layout}
|
||||
{occupiedSeatIds}
|
||||
selectedId={activeSeatId}
|
||||
{studentNames}
|
||||
onElementClick={handleSeatClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="note-editor">
|
||||
{#if activeSeatId}
|
||||
{@const studentName = studentNames[activeSeatId]}
|
||||
<h3>Note for {studentName || 'Empty Seat'}</h3>
|
||||
{#if studentName}
|
||||
<textarea bind:value={noteContent} placeholder="Enter observations..."></textarea>
|
||||
<button class="primary" onclick={saveNote}>Save Note</button>
|
||||
{:else}
|
||||
<p>Select a seat occupied by a student to leave a note.</p>
|
||||
{/if}
|
||||
<button onclick={() => activeSeatId = null}>Cancel</button>
|
||||
{:else}
|
||||
<p>Click a seat on the map to add or view notes.</p>
|
||||
{/if}
|
||||
|
||||
<div class="existing-notes">
|
||||
<h3>Recent Notes</h3>
|
||||
{#each notes as note}
|
||||
<div class="note-item">
|
||||
<strong>{students.find(s => s.id === note.student_id)?.name}:</strong>
|
||||
<p>{note.content}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.selectors { margin-bottom: 20px; }
|
||||
select { margin-right: 10px; padding: 5px; }
|
||||
.notes-container { display: grid; grid-template-columns: 1fr 300px; gap: 20px; }
|
||||
.note-editor { background: #f8f9fa; padding: 20px; border-radius: 8px; }
|
||||
textarea { width: 100%; height: 100px; margin: 10px 0; }
|
||||
.primary { background: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; }
|
||||
.note-item { font-size: 0.9em; border-bottom: 1px solid #ddd; padding: 5px 0; }
|
||||
.note-item p { margin: 5px 0; }
|
||||
</style>
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { api } from '$lib/api';
|
||||
import { token } from '$lib/auth';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let email = '';
|
||||
let password = '';
|
||||
let error = '';
|
||||
let loading = false;
|
||||
|
||||
async function login() {
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
const res = await api.auth.login(email, password);
|
||||
token.set(res.token);
|
||||
goto('/admin');
|
||||
} catch (e: any) {
|
||||
error = e.message || 'Invalid credentials';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="login-container">
|
||||
<h1>Tutor Login</h1>
|
||||
<form on:submit|preventDefault={login}>
|
||||
<div class="field">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" bind:value={email} required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" bind:value={password} required />
|
||||
</div>
|
||||
{#if error}
|
||||
<p class="error">{error}</p>
|
||||
{/if}
|
||||
<button type="submit" disabled={loading}>
|
||||
{loading ? 'Logging in...' : 'Login'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.login-container {
|
||||
max-width: 400px;
|
||||
margin: 100px auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.field {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user