Créneaux de nuit rattachés à leur journée de festival
Un créneau saisi « 00:00–01:00 » sur la carte du samedi était enregistré le samedi à 00:00 (nuit de vendredi à samedi), donc affiché la veille dans la grille des besoins. Une heure de début avant le début de journée (premier créneau type) désigne désormais la nuit qui suit : dimanche 00:00, à la suite de la soirée du samedi. Même « journée de festival » partout (validation/engine.py:day_boundary, festival_day ; miroir front lib/planning.ts:festivalDay) : écran Postes & créneaux, feuilles de poste (une seule page pour une soirée qui passe minuit) et chaînage du solveur (00h–01h enchaîne 23h–00h). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c60bc11795
commit
a6e7b6e7fb
@@ -3,6 +3,7 @@ import { useMemo, useState } from 'react';
|
||||
import {
|
||||
useCreatePosition,
|
||||
useCreateShift,
|
||||
useDayParts,
|
||||
useDeletePosition,
|
||||
useDeleteShift,
|
||||
useEdition,
|
||||
@@ -15,8 +16,13 @@ import {
|
||||
import type { PositionCategory, PositionRead, ShiftRead } from '@/api/schema';
|
||||
import { ErrorNote, Loading } from '@/components/state';
|
||||
import { Btn, HelpTip } from '@/components/ui';
|
||||
import { categoryLabel } from '@/lib/planning';
|
||||
import { addDays, hourRangeLabel, hoursBetween, longDayLabel } from '@/lib/time';
|
||||
import {
|
||||
categoryLabel,
|
||||
dayBoundaryFromDayParts,
|
||||
festivalDay,
|
||||
shiftDatesForFestivalDay,
|
||||
} from '@/lib/planning';
|
||||
import { hourRangeLabel, hoursBetween, longDayLabel } from '@/lib/time';
|
||||
import { t } from '@/i18n';
|
||||
import { cx } from '@/lib/cx';
|
||||
|
||||
@@ -47,6 +53,8 @@ export function ConfigGrid() {
|
||||
const editionQuery = useEdition();
|
||||
const positions = usePositions();
|
||||
const allShifts = useShifts();
|
||||
const dayParts = useDayParts();
|
||||
const dayBoundary = useMemo(() => dayBoundaryFromDayParts(dayParts.data ?? []), [dayParts.data]);
|
||||
const createPosition = useCreatePosition();
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
@@ -88,10 +96,17 @@ export function ConfigGrid() {
|
||||
);
|
||||
}
|
||||
|
||||
const shiftsByDay = groupByDay(positionShifts.data ?? []);
|
||||
const days = editionQuery.data
|
||||
? enumerateDays(editionQuery.data.start_date, editionQuery.data.end_date)
|
||||
: [...shiftsByDay.keys()];
|
||||
const shiftsByDay = groupByDay(positionShifts.data ?? [], dayBoundary);
|
||||
// Jours de l'édition, plus tout jour de festival qui porterait un créneau
|
||||
// hors de ces dates (sinon invisible ici, donc impossible à supprimer).
|
||||
const days = [
|
||||
...new Set([
|
||||
...(editionQuery.data
|
||||
? enumerateDays(editionQuery.data.start_date, editionQuery.data.end_date)
|
||||
: []),
|
||||
...shiftsByDay.keys(),
|
||||
]),
|
||||
].sort();
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 items-stretch">
|
||||
@@ -150,6 +165,7 @@ export function ConfigGrid() {
|
||||
day={day}
|
||||
positionId={position.id}
|
||||
shifts={shiftsByDay.get(day) ?? []}
|
||||
dayBoundary={dayBoundary}
|
||||
updateShift={updateShift}
|
||||
deleteShift={deleteShift}
|
||||
/>
|
||||
@@ -246,12 +262,14 @@ function DayCard({
|
||||
day,
|
||||
positionId,
|
||||
shifts,
|
||||
dayBoundary,
|
||||
updateShift,
|
||||
deleteShift,
|
||||
}: {
|
||||
day: string;
|
||||
positionId: string;
|
||||
shifts: ShiftRead[];
|
||||
dayBoundary: string;
|
||||
updateShift: ReturnType<typeof useUpdateShift>;
|
||||
deleteShift: ReturnType<typeof useDeleteShift>;
|
||||
}) {
|
||||
@@ -261,9 +279,8 @@ function DayCard({
|
||||
|
||||
const addShift = () => {
|
||||
if (!start || !end) return;
|
||||
const endDay = end <= start ? addDays(day, 1) : day;
|
||||
createShift.mutate(
|
||||
{ position_id: positionId, starts_at: `${day}T${start}:00`, ends_at: `${endDay}T${end}:00` },
|
||||
{ position_id: positionId, ...shiftDatesForFestivalDay(day, start, end, dayBoundary) },
|
||||
{ onSuccess: () => { setStart(''); setEnd(''); } },
|
||||
);
|
||||
};
|
||||
@@ -342,15 +359,22 @@ function DayCard({
|
||||
{t('config.grid.addShift')}
|
||||
</button>
|
||||
</div>
|
||||
{dayBoundary !== '00:00' ? (
|
||||
<div className="bg-sand px-[9px] pb-[7px] font-mono text-[9.5px] opacity-70">
|
||||
{t('config.grid.nightHint', { boundary: dayBoundary })}
|
||||
</div>
|
||||
) : null}
|
||||
{createShift.isError ? <ErrorNote error={createShift.error} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function groupByDay(shifts: ShiftRead[]): Map<string, ShiftRead[]> {
|
||||
/** Regroupe par journée de festival (pas par date du calendrier) : 00h–01h
|
||||
* le dimanche reste avec la soirée du samedi, triée après 23h. */
|
||||
function groupByDay(shifts: ShiftRead[], dayBoundary: string): Map<string, ShiftRead[]> {
|
||||
const map = new Map<string, ShiftRead[]>();
|
||||
for (const s of [...shifts].sort((a, b) => a.starts_at.localeCompare(b.starts_at))) {
|
||||
const day = s.starts_at.slice(0, 10);
|
||||
const day = festivalDay(s.starts_at, dayBoundary);
|
||||
map.set(day, [...(map.get(day) ?? []), s]);
|
||||
}
|
||||
return map;
|
||||
|
||||
Reference in New Issue
Block a user