refactor: make the reference week a dense one-row-per-day table
One card per day pushed the week to roughly 1100px, so checking a change meant scrolling back up past the day you had just edited. All seven days now fit on one phone screen. Each row carries the day, an open switch, its slots inline and its two actions. The per-row "duplicate onto the other open days" button was repeated five times in prose; it is now a short labelled control on the row itself, with the full sentence kept as its accessible name. Unsaved days are marked with a dot rather than a badge, and the footer still names which days are about to change — the point of the preview is that it is readable without leaving the week. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
This commit is contained in:
@@ -6,6 +6,7 @@ import { MAX_SLOTS_PER_DAY } from '@/lib/schedule/validate';
|
|||||||
import {
|
import {
|
||||||
changedDays,
|
changedDays,
|
||||||
DAY_NAMES_FR,
|
DAY_NAMES_FR,
|
||||||
|
DAY_SHORT_FR,
|
||||||
duplicateToOpenDays,
|
duplicateToOpenDays,
|
||||||
validateWeek,
|
validateWeek,
|
||||||
type WeekDraft,
|
type WeekDraft,
|
||||||
@@ -19,11 +20,11 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reference week.
|
* The reference week, as one row per day.
|
||||||
*
|
*
|
||||||
* Built for a phone held in one hand behind the counter: one card per day,
|
* Density is the point: all seven days have to be readable at a glance on a
|
||||||
* native time inputs so the platform keyboard does the work, and the
|
* phone held behind the counter, so the whole week fits on one screen and a
|
||||||
* consequences of a change shown before it is saved rather than after.
|
* change can be checked without scrolling back up.
|
||||||
*/
|
*/
|
||||||
export default function WeekEditor({ initial, canEdit }: Props) {
|
export default function WeekEditor({ initial, canEdit }: Props) {
|
||||||
const [week, setWeek] = useState<WeekDraft>(initial);
|
const [week, setWeek] = useState<WeekDraft>(initial);
|
||||||
@@ -36,15 +37,20 @@ export default function WeekEditor({ initial, canEdit }: Props) {
|
|||||||
const pendingChanges = useMemo(() => changedDays(saved, week), [saved, week]);
|
const pendingChanges = useMemo(() => changedDays(saved, week), [saved, week]);
|
||||||
const blocked = Object.keys(errors).length > 0;
|
const blocked = Object.keys(errors).length > 0;
|
||||||
|
|
||||||
function update(dayOfWeek: number, patch: Partial<WeekDraft[number]>) {
|
function touch() {
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
setServerErrors({});
|
setServerErrors({});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(dayOfWeek: number, patch: Partial<WeekDraft[number]>) {
|
||||||
|
touch();
|
||||||
setWeek((current) =>
|
setWeek((current) =>
|
||||||
current.map((day) => (day.dayOfWeek === dayOfWeek ? { ...day, ...patch } : day)),
|
current.map((day) => (day.dayOfWeek === dayOfWeek ? { ...day, ...patch } : day)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSlot(dayOfWeek: number, index: number, field: 'open' | 'close', value: string) {
|
function updateSlot(dayOfWeek: number, index: number, field: 'open' | 'close', value: string) {
|
||||||
|
touch();
|
||||||
setWeek((current) =>
|
setWeek((current) =>
|
||||||
current.map((day) =>
|
current.map((day) =>
|
||||||
day.dayOfWeek === dayOfWeek
|
day.dayOfWeek === dayOfWeek
|
||||||
@@ -57,36 +63,21 @@ export default function WeekEditor({ initial, canEdit }: Props) {
|
|||||||
: day,
|
: day,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setMessage(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSlot(dayOfWeek: number) {
|
|
||||||
update(dayOfWeek, {
|
|
||||||
slots: [
|
|
||||||
...(week.find((day) => day.dayOfWeek === dayOfWeek)?.slots ?? []),
|
|
||||||
{ open: '14:00', close: '18:30' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSlot(dayOfWeek: number, index: number) {
|
|
||||||
const day = week.find((entry) => entry.dayOfWeek === dayOfWeek);
|
|
||||||
update(dayOfWeek, { slots: (day?.slots ?? []).filter((_, position) => position !== index) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
setMessage(null);
|
touch();
|
||||||
setServerErrors({});
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
const result = await saveWeek(week);
|
const result = await saveWeek(week);
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
setSaved(week);
|
setSaved(week);
|
||||||
|
const count = result.changed.length;
|
||||||
setMessage({
|
setMessage({
|
||||||
kind: 'ok',
|
kind: 'ok',
|
||||||
text:
|
text:
|
||||||
result.changed.length === 0
|
count === 0
|
||||||
? 'Aucun changement à enregistrer.'
|
? 'Aucun changement à enregistrer.'
|
||||||
: `Horaires enregistrés (${result.changed.length} jour${result.changed.length > 1 ? 's' : ''} modifié${result.changed.length > 1 ? 's' : ''}).`,
|
: `Enregistré — ${count} jour${count > 1 ? 's' : ''} modifié${count > 1 ? 's' : ''}.`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setServerErrors(result.dayErrors ?? {});
|
setServerErrors(result.dayErrors ?? {});
|
||||||
@@ -96,32 +87,44 @@ export default function WeekEditor({ initial, canEdit }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-8">
|
<div className="mt-6">
|
||||||
{!canEdit ? (
|
{!canEdit ? (
|
||||||
<p className="mb-6 rounded-[var(--radius-md)] border border-[var(--line-strong)] bg-[var(--surface-muted)] px-4 py-3 text-sm text-[var(--ink-muted)]">
|
<p className="mb-4 rounded-[var(--radius-md)] border border-[var(--line-strong)] bg-[var(--surface-muted)] px-3 py-2 text-sm text-[var(--ink-muted)]">
|
||||||
Ce compte est en lecture seule : les horaires sont consultables mais pas modifiables.
|
Compte en lecture seule : consultation uniquement.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="overflow-hidden rounded-[var(--radius-md)] border border-[var(--line)] bg-[var(--surface)]">
|
||||||
{week.map((day) => {
|
{week.map((day, position) => {
|
||||||
const dayErrors = [...(errors[day.dayOfWeek] ?? []), ...(serverErrors[day.dayOfWeek] ?? [])];
|
const dayErrors = [
|
||||||
|
...(errors[day.dayOfWeek] ?? []),
|
||||||
|
...(serverErrors[day.dayOfWeek] ?? []),
|
||||||
|
];
|
||||||
const isChanged = pendingChanges.includes(day.dayOfWeek);
|
const isChanged = pendingChanges.includes(day.dayOfWeek);
|
||||||
|
const fullName = DAY_NAMES_FR[day.dayOfWeek] ?? '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<div
|
||||||
key={day.dayOfWeek}
|
key={day.dayOfWeek}
|
||||||
className={`rounded-[var(--radius-md)] border bg-[var(--surface)] p-4 ${
|
className={`${position > 0 ? 'border-t border-[var(--line)]' : ''} ${
|
||||||
dayErrors.length > 0 ? 'border-[var(--danger)]' : 'border-[var(--line)]'
|
dayErrors.length > 0 ? 'bg-[var(--danger-tint)]' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 px-3 py-2">
|
||||||
<h2 className="min-w-28 text-base font-medium">{DAY_NAMES_FR[day.dayOfWeek]}</h2>
|
<span
|
||||||
|
className={`w-9 shrink-0 text-sm font-medium tabular-nums ${
|
||||||
|
day.isClosed ? 'text-[var(--ink-muted)]' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{DAY_SHORT_FR[day.dayOfWeek]}</span>
|
||||||
|
<span className="sr-only">{fullName}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
<label className="flex items-center gap-2 text-sm">
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="size-4 accent-[var(--accent)]"
|
role="switch"
|
||||||
|
aria-label={`Ouvert le ${fullName.toLowerCase()}`}
|
||||||
|
className="size-4 shrink-0 accent-[var(--accent)]"
|
||||||
checked={!day.isClosed}
|
checked={!day.isClosed}
|
||||||
disabled={!canEdit}
|
disabled={!canEdit}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
@@ -134,100 +137,116 @@ export default function WeekEditor({ initial, canEdit }: Props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
Ouvert
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{isChanged ? (
|
|
||||||
<span className="rounded-[var(--radius-sm)] bg-[var(--accent-tint)] px-2 py-0.5 text-xs text-[var(--accent-ink)]">
|
|
||||||
modifié
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{day.isClosed ? (
|
{day.isClosed ? (
|
||||||
<p className="mt-3 text-sm text-[var(--ink-muted)]">Fermé toute la journée.</p>
|
<span className="text-sm text-[var(--ink-muted)]">Fermé</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-3 space-y-2">
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||||
{day.slots.map((slot, index) => (
|
{day.slots.map((slot, index) => (
|
||||||
<div key={index} className="flex flex-wrap items-center gap-2">
|
<span key={index} className="flex items-center gap-1">
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
aria-label={`${DAY_NAMES_FR[day.dayOfWeek]} — ouverture ${index + 1}`}
|
aria-label={`${fullName} — ouverture ${index + 1}`}
|
||||||
value={slot.open}
|
value={slot.open}
|
||||||
disabled={!canEdit}
|
disabled={!canEdit}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
updateSlot(day.dayOfWeek, index, 'open', event.target.value)
|
updateSlot(day.dayOfWeek, index, 'open', event.target.value)
|
||||||
}
|
}
|
||||||
className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] bg-[var(--surface)] px-2 py-1.5 text-base tabular-nums"
|
className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] bg-[var(--surface)] px-1.5 py-1 text-sm tabular-nums"
|
||||||
/>
|
/>
|
||||||
<span aria-hidden className="text-[var(--ink-muted)]">
|
<span aria-hidden className="text-[var(--ink-muted)]">
|
||||||
–
|
–
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
aria-label={`${DAY_NAMES_FR[day.dayOfWeek]} — fermeture ${index + 1}`}
|
aria-label={`${fullName} — fermeture ${index + 1}`}
|
||||||
value={slot.close}
|
value={slot.close}
|
||||||
disabled={!canEdit}
|
disabled={!canEdit}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
updateSlot(day.dayOfWeek, index, 'close', event.target.value)
|
updateSlot(day.dayOfWeek, index, 'close', event.target.value)
|
||||||
}
|
}
|
||||||
className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] bg-[var(--surface)] px-2 py-1.5 text-base tabular-nums"
|
className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] bg-[var(--surface)] px-1.5 py-1 text-sm tabular-nums"
|
||||||
/>
|
/>
|
||||||
{canEdit && day.slots.length > 1 ? (
|
{canEdit && day.slots.length > 1 ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeSlot(day.dayOfWeek, index)}
|
title={`Retirer cette plage du ${fullName.toLowerCase()}`}
|
||||||
className="rounded-[var(--radius-sm)] px-2 py-1.5 text-sm text-[var(--ink-muted)] underline-offset-2 hover:underline"
|
aria-label={`Retirer la plage ${index + 1} du ${fullName.toLowerCase()}`}
|
||||||
|
onClick={() =>
|
||||||
|
update(day.dayOfWeek, {
|
||||||
|
slots: day.slots.filter((_, at) => at !== index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="rounded-[var(--radius-sm)] px-1 text-[var(--ink-muted)] hover:text-[var(--danger)]"
|
||||||
>
|
>
|
||||||
Retirer
|
×
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{canEdit && day.slots.length < MAX_SLOTS_PER_DAY ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => addSlot(day.dayOfWeek)}
|
|
||||||
className="text-sm text-[var(--accent-ink)] underline-offset-2 hover:underline"
|
|
||||||
>
|
|
||||||
+ Ajouter une plage (pause de midi)
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{canEdit ? (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setWeek(duplicateToOpenDays(week, day.dayOfWeek))}
|
|
||||||
className="text-sm text-[var(--ink-muted)] underline-offset-2 hover:underline"
|
|
||||||
>
|
|
||||||
Dupliquer sur les autres jours ouverts
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{canEdit && !day.isClosed ? (
|
||||||
|
<span className="ml-auto flex shrink-0 items-center gap-1">
|
||||||
|
{day.slots.length < MAX_SLOTS_PER_DAY ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Ajouter une plage (pause de midi)"
|
||||||
|
aria-label={`Ajouter une plage le ${fullName.toLowerCase()}`}
|
||||||
|
onClick={() =>
|
||||||
|
update(day.dayOfWeek, {
|
||||||
|
slots: [...day.slots, { open: '14:00', close: '18:30' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] px-2 py-0.5 text-sm text-[var(--ink-muted)] hover:bg-[var(--surface-muted)] hover:text-[var(--ink)]"
|
||||||
|
>
|
||||||
|
+ plage
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Appliquer ces horaires aux autres jours ouverts"
|
||||||
|
aria-label={`Appliquer les horaires du ${fullName.toLowerCase()} aux autres jours ouverts`}
|
||||||
|
onClick={() => {
|
||||||
|
touch();
|
||||||
|
setWeek(duplicateToOpenDays(week, day.dayOfWeek));
|
||||||
|
}}
|
||||||
|
className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] px-2 py-0.5 text-sm text-[var(--ink-muted)] hover:bg-[var(--surface-muted)] hover:text-[var(--ink)]"
|
||||||
|
>
|
||||||
|
copier
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isChanged ? (
|
||||||
|
<span
|
||||||
|
aria-label="modifié"
|
||||||
|
title="Modifié, pas encore enregistré"
|
||||||
|
className="size-1.5 shrink-0 rounded-full bg-[var(--accent)]"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{dayErrors.length > 0 ? (
|
{dayErrors.length > 0 ? (
|
||||||
<ul role="alert" className="mt-3 space-y-1 text-sm text-[var(--danger)]">
|
<ul role="alert" className="px-3 pb-2 text-sm text-[var(--danger)]">
|
||||||
{dayErrors.map((error) => (
|
{dayErrors.map((error) => (
|
||||||
<li key={error}>{error}</li>
|
<li key={error}>{error}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<div className="sticky bottom-0 mt-6 flex flex-wrap items-center gap-3 border-t border-[var(--line)] bg-[var(--canvas)] py-4">
|
<div className="sticky bottom-0 mt-4 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-[var(--line)] bg-[var(--canvas)] py-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={pending || blocked || pendingChanges.length === 0}
|
disabled={pending || blocked || pendingChanges.length === 0}
|
||||||
className="rounded-[var(--radius-md)] bg-[var(--accent)] px-5 py-2.5 font-medium text-[var(--on-accent)] transition-colors hover:bg-[var(--accent-hover)] disabled:cursor-not-allowed disabled:opacity-40"
|
className="rounded-[var(--radius-md)] bg-[var(--accent)] px-4 py-2 font-medium text-[var(--on-accent)] transition-colors hover:bg-[var(--accent-hover)] disabled:cursor-not-allowed disabled:opacity-40"
|
||||||
>
|
>
|
||||||
{pending ? 'Enregistrement…' : 'Enregistrer'}
|
{pending ? 'Enregistrement…' : 'Enregistrer'}
|
||||||
</button>
|
</button>
|
||||||
@@ -237,23 +256,20 @@ export default function WeekEditor({ initial, canEdit }: Props) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setWeek(saved);
|
setWeek(saved);
|
||||||
setMessage(null);
|
touch();
|
||||||
setServerErrors({});
|
|
||||||
}}
|
}}
|
||||||
className="text-sm text-[var(--ink-muted)] underline-offset-2 hover:underline"
|
className="text-sm text-[var(--ink-muted)] underline-offset-2 hover:underline"
|
||||||
>
|
>
|
||||||
Annuler les modifications
|
Annuler
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<p className="text-sm text-[var(--ink-muted)]" aria-live="polite">
|
<p className="text-sm text-[var(--ink-muted)]" aria-live="polite">
|
||||||
{blocked
|
{blocked
|
||||||
? 'Corrigez les erreurs ci-dessus.'
|
? 'Corrigez les erreurs signalées.'
|
||||||
: pendingChanges.length === 0
|
: pendingChanges.length === 0
|
||||||
? 'Aucune modification.'
|
? 'Aucune modification.'
|
||||||
: `${pendingChanges.length} jour${pendingChanges.length > 1 ? 's' : ''} sera modifié : ${pendingChanges
|
: `Sera modifié : ${pendingChanges.map((d) => DAY_SHORT_FR[d]).join(', ')}.`}
|
||||||
.map((dayOfWeek) => DAY_NAMES_FR[dayOfWeek])
|
|
||||||
.join(', ')}.`}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -261,7 +277,7 @@ export default function WeekEditor({ initial, canEdit }: Props) {
|
|||||||
{message ? (
|
{message ? (
|
||||||
<p
|
<p
|
||||||
role="status"
|
role="status"
|
||||||
className={`mt-4 rounded-[var(--radius-md)] px-4 py-3 text-sm ${
|
className={`mt-3 rounded-[var(--radius-md)] px-3 py-2 text-sm ${
|
||||||
message.kind === 'ok'
|
message.kind === 'ok'
|
||||||
? 'bg-[var(--success-tint)] text-[var(--success)]'
|
? 'bg-[var(--success-tint)] text-[var(--success)]'
|
||||||
: 'bg-[var(--danger-tint)] text-[var(--danger)]'
|
: 'bg-[var(--danger-tint)] text-[var(--danger)]'
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ export default async function HoursPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-3xl px-4 py-10">
|
<main className="mx-auto max-w-3xl px-4 py-8">
|
||||||
<h1 className="text-2xl">Horaires fixes</h1>
|
<h1 className="text-2xl">Horaires fixes</h1>
|
||||||
<p className="mt-2 max-w-prose text-[var(--ink-muted)]">
|
<p className="mt-1 text-sm text-[var(--ink-muted)]">
|
||||||
La semaine de référence. Les fermetures ponctuelles, les vacances et les jours fériés se
|
Semaine de référence. Fermetures ponctuelles, vacances et jours fériés priment sur ces
|
||||||
règlent ailleurs et priment sur ces horaires.
|
horaires.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<WeekEditor initial={initial} canEdit={session?.user?.role === 'admin'} />
|
<WeekEditor initial={initial} canEdit={session?.user?.role === 'admin'} />
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ import { validateSlots } from './validate';
|
|||||||
/** Monday first: how the week is read, and how the screen draws it. */
|
/** Monday first: how the week is read, and how the screen draws it. */
|
||||||
export const WEEK_ORDER = [1, 2, 3, 4, 5, 6, 0] as const;
|
export const WEEK_ORDER = [1, 2, 3, 4, 5, 6, 0] as const;
|
||||||
|
|
||||||
|
/** Three letters, for the dense week table. */
|
||||||
|
export const DAY_SHORT_FR: Record<number, string> = {
|
||||||
|
0: 'Dim',
|
||||||
|
1: 'Lun',
|
||||||
|
2: 'Mar',
|
||||||
|
3: 'Mer',
|
||||||
|
4: 'Jeu',
|
||||||
|
5: 'Ven',
|
||||||
|
6: 'Sam',
|
||||||
|
};
|
||||||
|
|
||||||
export const DAY_NAMES_FR: Record<number, string> = {
|
export const DAY_NAMES_FR: Record<number, string> = {
|
||||||
0: 'Dimanche',
|
0: 'Dimanche',
|
||||||
1: 'Lundi',
|
1: 'Lundi',
|
||||||
|
|||||||
Reference in New Issue
Block a user