nice
This commit is contained in:
parent
503172f537
commit
ba5fd9bf85
@ -12,7 +12,7 @@ import {
|
|||||||
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
AlertDescription,
|
AlertDescription,
|
||||||
@ -334,54 +334,106 @@ const TimePicker = ({
|
|||||||
onChange,
|
onChange,
|
||||||
placeholder = 'Choisir une heure',
|
placeholder = 'Choisir une heure',
|
||||||
}) => {
|
}) => {
|
||||||
const [open, setOpen] =
|
const [open, setOpen] = useState(false);
|
||||||
useState(false);
|
|
||||||
|
|
||||||
const [
|
const [hours = '', minutes = ''] = value
|
||||||
hours = '',
|
|
||||||
minutes = '',
|
|
||||||
] = value
|
|
||||||
? value.split(':')
|
? value.split(':')
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const setTimePart = (
|
const hourOptions = Array.from(
|
||||||
part,
|
{ length: 24 },
|
||||||
nextValue,
|
(_, 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(
|
onChange(
|
||||||
`${nextHours}:${nextMinutes}`,
|
`${String(nextHours).padStart(2, '0')}:${String(
|
||||||
|
nextMinutes,
|
||||||
|
).padStart(2, '0')}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hourOptions =
|
const changeHour = (direction) => {
|
||||||
Array.from(
|
const currentHour = Number(hours || 0);
|
||||||
{ length: 24 },
|
const currentMinute = Number(
|
||||||
(_, index) =>
|
minutes || 0,
|
||||||
String(index).padStart(
|
|
||||||
2,
|
|
||||||
'0',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const minuteOptions =
|
let nextHour =
|
||||||
Array.from(
|
currentHour + direction;
|
||||||
{ length: 60 },
|
|
||||||
(_, index) =>
|
if (nextHour < 0) nextHour = 23;
|
||||||
String(index).padStart(
|
if (nextHour > 23) nextHour = 0;
|
||||||
2,
|
|
||||||
'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 (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
@ -402,7 +454,7 @@ const TimePicker = ({
|
|||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
value
|
value
|
||||||
? ''
|
? 'tabular-nums'
|
||||||
: 'text-muted-foreground'
|
: 'text-muted-foreground'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -411,102 +463,106 @@ const TimePicker = ({
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="w-[280px] p-3"
|
className="w-[190px] p-3"
|
||||||
align="start"
|
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">
|
<span className="text-sm font-medium">
|
||||||
Choisir une heure
|
Choisir une heure
|
||||||
</span>
|
</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 || '--:--'}
|
{value || '--:--'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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 */}
|
{/* HEURES */}
|
||||||
|
<div
|
||||||
<div>
|
className="flex-1"
|
||||||
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
onWheel={(event) =>
|
||||||
Heure
|
handleWheel(
|
||||||
</p>
|
event,
|
||||||
|
|
||||||
<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(
|
|
||||||
'hours',
|
'hours',
|
||||||
hour,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{hour}
|
<div className="flex h-full flex-col items-center justify-center select-none">
|
||||||
</Button>
|
<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>
|
</div>
|
||||||
|
|
||||||
|
{/* : */}
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<span className="font-bold">
|
||||||
|
:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* MINUTES */}
|
{/* MINUTES */}
|
||||||
|
<div
|
||||||
<div>
|
className="flex-1"
|
||||||
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
onWheel={(event) =>
|
||||||
Minutes
|
handleWheel(
|
||||||
</p>
|
event,
|
||||||
|
|
||||||
<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(
|
|
||||||
'minutes',
|
'minutes',
|
||||||
minute,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{minute}
|
<div className="flex h-full flex-col items-center justify-center select-none">
|
||||||
</Button>
|
<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>
|
</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
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="ghost"
|
||||||
className="w-full"
|
size="sm"
|
||||||
|
className="h-7 w-full text-xs"
|
||||||
disabled={!value}
|
disabled={!value}
|
||||||
onClick={() => {
|
onClick={clearTime}
|
||||||
onChange('');
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Effacer
|
Effacer
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -42,16 +42,6 @@ const MOBILE_TIME_COLUMN_WIDTH = 64;
|
|||||||
const MOBILE_USER_COLUMN_WIDTH = 40;
|
const MOBILE_USER_COLUMN_WIDTH = 40;
|
||||||
const MOBILE_DEFAULT_CONTENT_WIDTH = 236;
|
const MOBILE_DEFAULT_CONTENT_WIDTH = 236;
|
||||||
|
|
||||||
const USER_COLORS = [
|
|
||||||
'#c56a3d',
|
|
||||||
'#3d7c8c',
|
|
||||||
'#8a5a9e',
|
|
||||||
'#5d8b55',
|
|
||||||
'#c08a32',
|
|
||||||
'#b04f66',
|
|
||||||
'#4772a8',
|
|
||||||
];
|
|
||||||
|
|
||||||
const expandOvernightAttendances = (entries) => {
|
const expandOvernightAttendances = (entries) => {
|
||||||
return entries.map((entry) => {
|
return entries.map((entry) => {
|
||||||
if (entry.start_time && !entry.end_time) {
|
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 () => {
|
const loadAttendances = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -1214,7 +1169,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
).map((user) => ({
|
).map((user) => ({
|
||||||
id: getUserId(user),
|
id: getUserId(user),
|
||||||
label: getUserLabel(user),
|
label: getUserLabel(user),
|
||||||
color: getUserColor(user),
|
color: user.color,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1599,10 +1554,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
|
|
||||||
const color =
|
const color = user.color;
|
||||||
getUserColor(
|
|
||||||
user,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -1720,6 +1672,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
style={
|
style={
|
||||||
style
|
style
|
||||||
}
|
}
|
||||||
|
title={`${formatSegmentTime(start)} - ${formatSegmentTime(end)}`}
|
||||||
>
|
>
|
||||||
<div className="p-1 text-xs text-white">
|
<div className="p-1 text-xs text-white">
|
||||||
{formatSegmentTime(
|
{formatSegmentTime(
|
||||||
@ -1873,10 +1826,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
|
|
||||||
const color =
|
const color = user.color;
|
||||||
getUserColor(
|
|
||||||
user,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -1993,6 +1943,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
style={
|
style={
|
||||||
style
|
style
|
||||||
}
|
}
|
||||||
|
title={`${formatSegmentTime(start)} - ${formatSegmentTime(end)}`}
|
||||||
>
|
>
|
||||||
<div className="p-2 text-xs text-white">
|
<div className="p-2 text-xs text-white">
|
||||||
<div className="font-medium">
|
<div className="font-medium">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user