This commit is contained in:
Louis Mazin 2026-09-20 04:41:44 +02:00
parent 503172f537
commit ba5fd9bf85
2 changed files with 167 additions and 160 deletions

View File

@ -12,7 +12,7 @@ import {
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Alert,
AlertDescription,
@ -334,54 +334,106 @@ const TimePicker = ({
onChange,
placeholder = 'Choisir une heure',
}) => {
const [open, setOpen] =
useState(false);
const [open, setOpen] = useState(false);
const [
hours = '',
minutes = '',
] = value
const [hours = '', minutes = ''] = value
? value.split(':')
: [];
const setTimePart = (
part,
nextValue,
const hourOptions = Array.from(
{ length: 24 },
(_, i) => String(i).padStart(2, '0'),
);
const minuteOptions = Array.from(
{ length: 60 },
(_, i) => String(i).padStart(2, '0'),
);
const updateTime = (
nextHours,
nextMinutes,
) => {
const nextHours =
part === 'hours'
? nextValue
: hours || '00';
const nextMinutes =
part === 'minutes'
? nextValue
: minutes || '00';
onChange(
`${nextHours}:${nextMinutes}`,
`${String(nextHours).padStart(2, '0')}:${String(
nextMinutes,
).padStart(2, '0')}`,
);
};
const hourOptions =
Array.from(
{ length: 24 },
(_, index) =>
String(index).padStart(
2,
'0',
),
const changeHour = (direction) => {
const currentHour = Number(hours || 0);
const currentMinute = Number(
minutes || 0,
);
const minuteOptions =
Array.from(
{ length: 60 },
(_, index) =>
String(index).padStart(
2,
'0',
),
let nextHour =
currentHour + direction;
if (nextHour < 0) nextHour = 23;
if (nextHour > 23) nextHour = 0;
updateTime(
nextHour,
currentMinute,
);
};
const changeMinute = (direction) => {
const currentHour = Number(hours || 0);
const currentMinute = Number(
minutes || 0,
);
let nextMinute =
currentMinute + direction;
let nextHour = currentHour;
if (nextMinute < 0) {
nextMinute = 59;
nextHour =
currentHour === 0
? 23
: currentHour - 1;
}
if (nextMinute > 59) {
nextMinute = 0;
nextHour =
currentHour === 23
? 0
: currentHour + 1;
}
updateTime(
nextHour,
nextMinute,
);
};
const handleWheel = (
event,
type,
) => {
event.preventDefault();
event.stopPropagation();
// Un événement de molette = une seule unité
const direction =
event.deltaY > 0 ? 1 : -1;
if (type === 'hours') {
changeHour(direction);
} else {
changeMinute(direction);
}
};
const clearTime = () => {
onChange('');
setOpen(false);
};
return (
<Popover
@ -402,7 +454,7 @@ const TimePicker = ({
<span
className={
value
? ''
? 'tabular-nums'
: 'text-muted-foreground'
}
>
@ -411,102 +463,106 @@ const TimePicker = ({
</PopoverTrigger>
<PopoverContent
className="w-[280px] p-3"
className="w-[190px] p-3"
align="start"
>
<div className="mb-3 flex items-center justify-between border-b pb-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-medium">
Choisir une heure
</span>
<span className="rounded-md bg-muted px-2 py-1 text-sm font-medium tabular-nums">
<span className="font-mono text-sm font-semibold tabular-nums">
{value || '--:--'}
</span>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="relative flex h-[144px] gap-2 overflow-hidden">
{/* Sélection centrale */}
<div className="pointer-events-none absolute inset-x-0 top-1/2 z-10 h-9 -translate-y-1/2 rounded-md border bg-muted/50" />
{/* HEURES */}
<div>
<p className="mb-2 text-xs font-medium text-muted-foreground">
Heure
</p>
<div className="grid max-h-56 grid-cols-4 gap-1 overflow-y-auto pr-1">
{hourOptions.map(
(hour) => (
<Button
key={hour}
type="button"
size="sm"
variant={
hours === hour
? 'default'
: 'ghost'
}
className="h-8 px-0 tabular-nums"
onClick={() =>
setTimePart(
<div
className="flex-1"
onWheel={(event) =>
handleWheel(
event,
'hours',
hour,
)
}
>
{hour}
</Button>
),
)}
<div className="flex h-full flex-col items-center justify-center select-none">
<span className="h-9 text-sm text-muted-foreground/40">
{hourOptions[
(Number(hours || 0) + 23) %
24
]}
</span>
<span className="flex h-9 w-full items-center justify-center rounded-md font-mono text-sm font-semibold tabular-nums">
{hours || '00'}
</span>
<span className="h-9 text-sm text-muted-foreground/40">
{hourOptions[
(Number(hours || 0) + 1) %
24
]}
</span>
</div>
</div>
{/* : */}
<div className="flex items-center justify-center">
<span className="font-bold">
:
</span>
</div>
{/* MINUTES */}
<div>
<p className="mb-2 text-xs font-medium text-muted-foreground">
Minutes
</p>
<div className="grid max-h-56 grid-cols-4 gap-1 overflow-y-auto pr-1">
{minuteOptions.map(
(minute) => (
<Button
key={minute}
type="button"
size="sm"
variant={
minutes === minute
? 'default'
: 'ghost'
}
className="h-8 px-0 tabular-nums"
onClick={() =>
setTimePart(
<div
className="flex-1"
onWheel={(event) =>
handleWheel(
event,
'minutes',
minute,
)
}
>
{minute}
</Button>
),
)}
<div className="flex h-full flex-col items-center justify-center select-none">
<span className="h-9 text-sm text-muted-foreground/40">
{minuteOptions[
(Number(minutes || 0) +
59) %
60
]}
</span>
<span className="flex h-9 w-full items-center justify-center rounded-md font-mono text-sm font-semibold tabular-nums">
{minutes || '00'}
</span>
<span className="h-9 text-sm text-muted-foreground/40">
{minuteOptions[
(Number(minutes || 0) + 1) %
60
]}
</span>
</div>
</div>
</div>
</div>
<p className="mt-2 text-center text-[10px] text-muted-foreground">
Molette pour modifier
</p>
<div className="mt-3 border-t pt-3">
<div className="mt-2 border-t pt-2">
<Button
type="button"
variant="secondary"
className="w-full"
variant="ghost"
size="sm"
className="h-7 w-full text-xs"
disabled={!value}
onClick={() => {
onChange('');
setOpen(false);
}}
onClick={clearTime}
>
Effacer
</Button>

View File

@ -42,16 +42,6 @@ const MOBILE_TIME_COLUMN_WIDTH = 64;
const MOBILE_USER_COLUMN_WIDTH = 40;
const MOBILE_DEFAULT_CONTENT_WIDTH = 236;
const USER_COLORS = [
'#c56a3d',
'#3d7c8c',
'#8a5a9e',
'#5d8b55',
'#c08a32',
'#b04f66',
'#4772a8',
];
const expandOvernightAttendances = (entries) => {
return entries.map((entry) => {
if (entry.start_time && !entry.end_time) {
@ -241,41 +231,6 @@ const WeeklySummaryPage = ({ userId }) => {
};
};
const getUserColor = (user) => {
if (user?.color) {
return user.color;
}
const id = getUserId(user);
if (id) {
const numericId = Number(id);
if (!Number.isNaN(numericId)) {
return USER_COLORS[
numericId % USER_COLORS.length
];
}
}
const name =
user?.first_name ||
user?.firstName ||
user?.username ||
user?.name ||
'';
const hash = [...String(name)].reduce(
(total, character) =>
total + character.charCodeAt(0),
0,
);
return USER_COLORS[
hash % USER_COLORS.length
];
};
const loadAttendances = async () => {
try {
setLoading(true);
@ -1214,7 +1169,7 @@ const WeeklySummaryPage = ({ userId }) => {
).map((user) => ({
id: getUserId(user),
label: getUserLabel(user),
color: getUserColor(user),
color: user.color,
}));
return (
@ -1599,10 +1554,7 @@ const WeeklySummaryPage = ({ userId }) => {
user,
);
const color =
getUserColor(
user,
);
const color = user.color;
return (
<div
@ -1720,6 +1672,7 @@ const WeeklySummaryPage = ({ userId }) => {
style={
style
}
title={`${formatSegmentTime(start)} - ${formatSegmentTime(end)}`}
>
<div className="p-1 text-xs text-white">
{formatSegmentTime(
@ -1873,10 +1826,7 @@ const WeeklySummaryPage = ({ userId }) => {
user,
);
const color =
getUserColor(
user,
);
const color = user.color;
return (
<div
@ -1993,6 +1943,7 @@ const WeeklySummaryPage = ({ userId }) => {
style={
style
}
title={`${formatSegmentTime(start)} - ${formatSegmentTime(end)}`}
>
<div className="p-2 text-xs text-white">
<div className="font-medium">