This commit is contained in:
Louis Mazin 2026-09-15 20:18:58 +02:00
parent 9d61e6f842
commit 923fbf1fbc
2 changed files with 100 additions and 5 deletions

View File

@ -4,6 +4,12 @@ import './TimeClockPage.css';
import { timeApi } from '../api/time';
import { useAuth } from '../context/AuthContext';
const getNextDateKey = (dateKey) => {
const nextDate = new Date(`${dateKey}T12:00:00`);
nextDate.setDate(nextDate.getDate() + 1);
return nextDate.toISOString().slice(0, 10);
};
const TimeClockPage = () => {
const videoRef = useRef(null);
const streamRef = useRef(null);
@ -269,9 +275,53 @@ const TimeClockPage = () => {
const formatTime = (timeString) => {
if (!timeString) return '00h00';
const [hours, minutes] = timeString.split(':');
if (hours === '24') return '23h59';
return `${String(hours).padStart(2, '0')}h${String(minutes).padStart(2, '0')}`;
};
const getDisplayTimeEntries = () => {
const pairedEndIds = new Set();
const expanded = [];
const toMinutes = (time) => {
const [hours, minutes] = time.split(':').map(Number);
return hours * 60 + minutes;
};
timeEntries.forEach((entry) => {
if (!entry.start_time && entry.end_time) return;
if (entry.start_time && !entry.end_time) {
const dateKey = String(entry.date).slice(0, 10);
const matchingEnd = timeEntries.find((candidate) => (
candidate.id !== entry.id &&
!candidate.start_time &&
candidate.end_time &&
String(candidate.date).slice(0, 10) === getNextDateKey(dateKey) &&
(candidate.user === entry.user || candidate.username === entry.username)
));
if (matchingEnd) {
pairedEndIds.add(matchingEnd.id);
expanded.push({
...entry,
end_time: '24:00:00',
duration: (1440 - toMinutes(entry.start_time)) / 60,
});
expanded.push({
...matchingEnd,
start_time: '00:00:00',
duration: toMinutes(matchingEnd.end_time) / 60,
});
return;
}
}
expanded.push(entry);
});
return expanded.filter((entry) => !pairedEndIds.has(entry.id));
};
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('fr-FR');
@ -446,7 +496,7 @@ const TimeClockPage = () => {
</tr>
</thead>
<tbody>
{timeEntries.map((entry) => (
{getDisplayTimeEntries().map((entry) => (
<tr key={entry.id}>
{isAdmin &&
<td>{entry.user}</td>

View File

@ -8,6 +8,46 @@ const DEFAULT_END_HOUR = 18;
const HOUR_HEIGHT = 64;
const USER_COLORS = ['#c56a3d', '#3d7c8c', '#8a5a9e', '#5d8b55', '#c08a32', '#b04f66', '#4772a8'];
const getNextDateKey = (dateKey) => {
const nextDate = new Date(`${dateKey}T12:00:00`);
nextDate.setDate(nextDate.getDate() + 1);
return nextDate.toISOString().slice(0, 10);
};
const expandOvernightAttendances = (entries) => {
const pairedEndIds = new Set();
const expanded = [];
entries.forEach((entry) => {
if (!entry.start_time && entry.end_time) {
return;
}
if (entry.start_time && !entry.end_time) {
const dateKey = String(entry.date).slice(0, 10);
const nextDateKey = getNextDateKey(dateKey);
const matchingEnd = entries.find((candidate) => (
candidate.id !== entry.id &&
!candidate.start_time &&
candidate.end_time &&
String(candidate.date).slice(0, 10) === nextDateKey &&
(candidate.user === entry.user || candidate.username === entry.username)
));
if (matchingEnd) {
pairedEndIds.add(matchingEnd.id);
expanded.push({ ...entry, end_time: '24:00:00' });
expanded.push({ ...matchingEnd, start_time: '00:00:00' });
return;
}
}
expanded.push(entry);
});
return expanded.filter((entry) => !pairedEndIds.has(entry.id));
};
const WeeklySummaryPage = ({ userId }) => {
const { isAdmin } = useAuth();
const [attendances, setAttendances] = useState([]);
@ -98,6 +138,11 @@ const WeeklySummaryPage = ({ userId }) => {
return (hours * 60) + minutes;
};
const formatSegmentTime = (minutes) => {
if (minutes === 1440) return '23h59';
return `${String(Math.floor(minutes / 60)).padStart(2, '0')}h${String(minutes % 60).padStart(2, '0')}`;
};
const getUserColor = (username) => {
const hash = [...username].reduce((total, character) => total + character.charCodeAt(0), 0);
return USER_COLORS[hash % USER_COLORS.length];
@ -105,7 +150,7 @@ const WeeklySummaryPage = ({ userId }) => {
const getAttendancesForDay = (date) => {
const dateKey = formatDateKey(date);
return attendances.filter((attendance) => String(attendance.date).slice(0, 10) === dateKey);
return expandOvernightAttendances(attendances).filter((attendance) => String(attendance.date).slice(0, 10) === dateKey);
};
const getAttendanceSegments = (dayAttendances) => {
@ -144,7 +189,7 @@ const WeeklySummaryPage = ({ userId }) => {
const getScheduleBounds = (weekDays) => {
const weekDateKeys = new Set(weekDays.map(formatDateKey));
const weekAttendances = attendances.filter((attendance) => (
const weekAttendances = expandOvernightAttendances(attendances).filter((attendance) => (
weekDateKeys.has(String(attendance.date).slice(0, 10))
));
const times = weekAttendances.flatMap((attendance) => [
@ -246,8 +291,8 @@ const WeeklySummaryPage = ({ userId }) => {
<div className="schedule-day-column" key={formatDateKey(day)}>
{hours.map((hour) => <span className="schedule-hour-line" key={hour} />)}
{getAttendanceSegments(getAttendancesForDay(day)).map(({ start, end, users }) => {
const startTime = `${String(Math.floor(start / 60)).padStart(2, '0')}:${String(start % 60).padStart(2, '0')}`;
const endTime = `${String(Math.floor(end / 60)).padStart(2, '0')}:${String(end % 60).padStart(2, '0')}`;
const startTime = formatSegmentTime(start);
const endTime = formatSegmentTime(end);
const segmentLabel = users.map(({ username }) => username).join(', ');
return (
isAdmin ? (