feat: Afficher la couleur des matières dans l'emploi du temps élève et parent
L'admin pouvait attribuer une couleur à chaque matière, mais cette couleur n'était utilisée que dans la vue admin de l'emploi du temps. Les APIs élève et parent ne renvoyaient pas cette information, ce qui donnait un affichage générique (gris/bleu) pour tous les créneaux. L'API renvoie désormais subjectColor dans chaque créneau, et les vues jour/semaine/widget/détails affichent la bordure colorée correspondante. Le marqueur "Prochain cours" conserve sa priorité visuelle via une surcharge CSS variable.
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
let {
|
||||
onChildSelected
|
||||
}: {
|
||||
onChildSelected?: (childId: string) => void;
|
||||
onChildSelected?: (childId: string | null) => void;
|
||||
} = $props();
|
||||
|
||||
let children = $state<Child[]>([]);
|
||||
@@ -40,8 +40,9 @@
|
||||
const data = await response.json();
|
||||
children = data['hydra:member'] ?? data['member'] ?? (Array.isArray(data) ? data : []);
|
||||
|
||||
// Auto-select only when there's exactly 1 child
|
||||
const first = children[0];
|
||||
if (first && !selectedChildId) {
|
||||
if (first && !selectedChildId && children.length === 1) {
|
||||
selectedChildId = first.studentId;
|
||||
onChildSelected?.(first.studentId);
|
||||
}
|
||||
@@ -52,7 +53,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function selectChild(childId: string) {
|
||||
function selectChild(childId: string | null) {
|
||||
selectedChildId = childId;
|
||||
onChildSelected?.(childId);
|
||||
}
|
||||
@@ -75,6 +76,13 @@
|
||||
<div class="child-selector">
|
||||
<span class="child-selector-label">Enfant :</span>
|
||||
<div class="child-selector-buttons">
|
||||
<button
|
||||
class="child-button"
|
||||
class:selected={selectedChildId === null}
|
||||
onclick={() => selectChild(null)}
|
||||
>
|
||||
Tous
|
||||
</button>
|
||||
{#each children as child (child.id)}
|
||||
<button
|
||||
class="child-button"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { DemoData } from '$types';
|
||||
import type { ScheduleSlot } from '$lib/features/schedule/api/schedule';
|
||||
import { fetchChildDaySchedule } from '$lib/features/schedule/api/parentSchedule';
|
||||
import { recordSync } from '$lib/features/schedule/stores/scheduleCache';
|
||||
import { recordSync } from '$lib/features/schedule/stores/scheduleCache.svelte';
|
||||
import SerenityScorePreview from '$lib/components/molecules/SerenityScore/SerenityScorePreview.svelte';
|
||||
import SerenityScoreExplainer from '$lib/components/molecules/SerenityScore/SerenityScoreExplainer.svelte';
|
||||
import DashboardSection from '$lib/components/molecules/DashboardSection.svelte';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { DemoData } from '$types';
|
||||
import type { ScheduleSlot } from '$lib/features/schedule/api/schedule';
|
||||
import { fetchDaySchedule, fetchNextClass } from '$lib/features/schedule/api/schedule';
|
||||
import { recordSync } from '$lib/features/schedule/stores/scheduleCache';
|
||||
import { recordSync } from '$lib/features/schedule/stores/scheduleCache.svelte';
|
||||
import DashboardSection from '$lib/components/molecules/DashboardSection.svelte';
|
||||
import SkeletonList from '$lib/components/atoms/Skeleton/SkeletonList.svelte';
|
||||
import ScheduleWidget from '$lib/components/organisms/StudentSchedule/ScheduleWidget.svelte';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { ChildScheduleSummary } from '$lib/features/schedule/api/parentSchedule';
|
||||
import { fetchChildrenScheduleSummary } from '$lib/features/schedule/api/parentSchedule';
|
||||
import { formatSyncDate } from '$lib/features/schedule/formatSyncDate';
|
||||
import { isOffline, getLastSyncDate } from '$lib/features/schedule/stores/scheduleCache';
|
||||
import { isOffline, getLastSyncDate, recordSync } from '$lib/features/schedule/stores/scheduleCache.svelte';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
let {
|
||||
@@ -43,6 +43,7 @@
|
||||
|
||||
try {
|
||||
children = await fetchChildrenScheduleSummary();
|
||||
recordSync();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Erreur de chargement';
|
||||
} finally {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { ScheduleSlot } from '$lib/features/schedule/api/schedule';
|
||||
import { fetchChildDaySchedule, fetchChildWeekSchedule } from '$lib/features/schedule/api/parentSchedule';
|
||||
import { untrack } from 'svelte';
|
||||
import { recordSync, isOffline, getLastSyncDate, prefetchScheduleDays } from '$lib/features/schedule/stores/scheduleCache';
|
||||
import { recordSync, isOffline, getLastSyncDate, prefetchScheduleDays } from '$lib/features/schedule/stores/scheduleCache.svelte';
|
||||
import { formatSyncDate } from '$lib/features/schedule/formatSyncDate';
|
||||
import ChildSelector from '$lib/components/organisms/ChildSelector/ChildSelector.svelte';
|
||||
import MultiChildSummary from '$lib/components/organisms/ParentSchedule/MultiChildSummary.svelte';
|
||||
@@ -88,13 +88,15 @@
|
||||
};
|
||||
});
|
||||
|
||||
function handleChildSelected(childId: string) {
|
||||
function handleChildSelected(childId: string | null) {
|
||||
selectedChildId = childId;
|
||||
untrack(() => {
|
||||
loadSchedule();
|
||||
// Prefetch for offline support
|
||||
prefetchScheduleDays((date) => fetchChildDaySchedule(childId, date));
|
||||
});
|
||||
if (childId) {
|
||||
untrack(() => {
|
||||
loadSchedule();
|
||||
// Prefetch for offline support
|
||||
prefetchScheduleDays((date) => fetchChildDaySchedule(childId, date));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function navigateDay(offset: number) {
|
||||
|
||||
@@ -44,7 +44,11 @@
|
||||
{:else}
|
||||
<ul class="slot-list">
|
||||
{#each slots as slot (slot.slotId + slot.date)}
|
||||
<li class="slot-item" class:next={slot.slotId === nextSlotId}>
|
||||
<li
|
||||
class="slot-item"
|
||||
class:next={slot.slotId === nextSlotId}
|
||||
style:--slot-color={slot.subjectColor ?? '#e5e7eb'}
|
||||
>
|
||||
<button
|
||||
class="slot-button"
|
||||
onclick={() => onSlotClick(slot)}
|
||||
@@ -123,12 +127,12 @@
|
||||
.slot-item {
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
border-left: 4px solid #e5e7eb;
|
||||
border-left: 4px solid var(--slot-color, #e5e7eb);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.slot-item.next {
|
||||
border-left-color: #3b82f6;
|
||||
--slot-color: #3b82f6;
|
||||
}
|
||||
|
||||
.slot-button {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { ScheduleSlot } from '$lib/features/schedule/api/schedule';
|
||||
import { formatSyncDate } from '$lib/features/schedule/formatSyncDate';
|
||||
import { isOffline, getLastSyncDate } from '$lib/features/schedule/stores/scheduleCache';
|
||||
import { isOffline, getLastSyncDate } from '$lib/features/schedule/stores/scheduleCache.svelte';
|
||||
|
||||
let {
|
||||
slots = [],
|
||||
@@ -61,6 +61,7 @@
|
||||
class="slot-item"
|
||||
class:next={slot.slotId === nextSlotId}
|
||||
data-testid="schedule-slot"
|
||||
style:--slot-color={slot.subjectColor ?? 'transparent'}
|
||||
>
|
||||
<div class="slot-time">
|
||||
<span class="time-start">{slot.startTime}</span>
|
||||
@@ -144,12 +145,12 @@
|
||||
background: #f9fafb;
|
||||
border-radius: 0.5rem;
|
||||
align-items: center;
|
||||
border-left: 3px solid transparent;
|
||||
border-left: 3px solid var(--slot-color, transparent);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slot-item.next {
|
||||
border-left-color: #3b82f6;
|
||||
--slot-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
aria-label="Fermer">×</button
|
||||
>
|
||||
|
||||
<h2 class="subject-name">{slot.subjectName}</h2>
|
||||
<h2 class="subject-name" class:has-color={slot.subjectColor} style:--slot-color={slot.subjectColor}>{slot.subjectName}</h2>
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="detail-row">
|
||||
@@ -149,6 +149,11 @@
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.subject-name.has-color {
|
||||
border-left: 4px solid var(--slot-color);
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { fetchDaySchedule, fetchWeekSchedule, fetchNextClass } from '$lib/features/schedule/api/schedule';
|
||||
import { formatSyncDate } from '$lib/features/schedule/formatSyncDate';
|
||||
import { untrack } from 'svelte';
|
||||
import { recordSync, isOffline, getLastSyncDate, prefetchScheduleDays } from '$lib/features/schedule/stores/scheduleCache';
|
||||
import { recordSync, isOffline, getLastSyncDate, prefetchScheduleDays } from '$lib/features/schedule/stores/scheduleCache.svelte';
|
||||
import DayView from './DayView.svelte';
|
||||
import WeekView from './WeekView.svelte';
|
||||
import SlotDetails from './SlotDetails.svelte';
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
class:modified={slot.isModified}
|
||||
onclick={() => onSlotClick(slot)}
|
||||
data-testid="week-slot"
|
||||
style:--slot-color={slot.subjectColor ?? '#e5e7eb'}
|
||||
>
|
||||
<span class="slot-time-mobile">{slot.startTime} - {slot.endTime}</span>
|
||||
<span class="slot-subject-mobile">{slot.subjectName}</span>
|
||||
@@ -97,6 +98,7 @@
|
||||
class:modified={slot.isModified}
|
||||
onclick={() => onSlotClick(slot)}
|
||||
data-testid="week-slot"
|
||||
style:--slot-color={slot.subjectColor ?? '#e5e7eb'}
|
||||
>
|
||||
<span class="week-slot-time">{slot.startTime}</span>
|
||||
<span class="week-slot-subject">{slot.subjectName}</span>
|
||||
@@ -182,6 +184,7 @@
|
||||
background: white;
|
||||
border: none;
|
||||
border-top: 1px solid #f3f4f6;
|
||||
border-left: 4px solid var(--slot-color, #e5e7eb);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
@@ -283,6 +286,7 @@
|
||||
padding: 0.5rem;
|
||||
background: #eff6ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-left: 4px solid var(--slot-color, #bfdbfe);
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
|
||||
Reference in New Issue
Block a user