test
This commit is contained in:
parent
9d61e6f842
commit
923fbf1fbc
@ -4,6 +4,12 @@ import './TimeClockPage.css';
|
|||||||
import { timeApi } from '../api/time';
|
import { timeApi } from '../api/time';
|
||||||
import { useAuth } from '../context/AuthContext';
|
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 TimeClockPage = () => {
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const streamRef = useRef(null);
|
const streamRef = useRef(null);
|
||||||
@ -269,9 +275,53 @@ const TimeClockPage = () => {
|
|||||||
const formatTime = (timeString) => {
|
const formatTime = (timeString) => {
|
||||||
if (!timeString) return '00h00';
|
if (!timeString) return '00h00';
|
||||||
const [hours, minutes] = timeString.split(':');
|
const [hours, minutes] = timeString.split(':');
|
||||||
|
if (hours === '24') return '23h59';
|
||||||
return `${String(hours).padStart(2, '0')}h${String(minutes).padStart(2, '0')}`;
|
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 formatDate = (dateString) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
return date.toLocaleDateString('fr-FR');
|
return date.toLocaleDateString('fr-FR');
|
||||||
@ -446,7 +496,7 @@ const TimeClockPage = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{timeEntries.map((entry) => (
|
{getDisplayTimeEntries().map((entry) => (
|
||||||
<tr key={entry.id}>
|
<tr key={entry.id}>
|
||||||
{isAdmin &&
|
{isAdmin &&
|
||||||
<td>{entry.user}</td>
|
<td>{entry.user}</td>
|
||||||
|
|||||||
@ -8,6 +8,46 @@ const DEFAULT_END_HOUR = 18;
|
|||||||
const HOUR_HEIGHT = 64;
|
const HOUR_HEIGHT = 64;
|
||||||
const USER_COLORS = ['#c56a3d', '#3d7c8c', '#8a5a9e', '#5d8b55', '#c08a32', '#b04f66', '#4772a8'];
|
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 WeeklySummaryPage = ({ userId }) => {
|
||||||
const { isAdmin } = useAuth();
|
const { isAdmin } = useAuth();
|
||||||
const [attendances, setAttendances] = useState([]);
|
const [attendances, setAttendances] = useState([]);
|
||||||
@ -98,6 +138,11 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
return (hours * 60) + minutes;
|
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 getUserColor = (username) => {
|
||||||
const hash = [...username].reduce((total, character) => total + character.charCodeAt(0), 0);
|
const hash = [...username].reduce((total, character) => total + character.charCodeAt(0), 0);
|
||||||
return USER_COLORS[hash % USER_COLORS.length];
|
return USER_COLORS[hash % USER_COLORS.length];
|
||||||
@ -105,7 +150,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
|
|
||||||
const getAttendancesForDay = (date) => {
|
const getAttendancesForDay = (date) => {
|
||||||
const dateKey = formatDateKey(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) => {
|
const getAttendanceSegments = (dayAttendances) => {
|
||||||
@ -144,7 +189,7 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
|
|
||||||
const getScheduleBounds = (weekDays) => {
|
const getScheduleBounds = (weekDays) => {
|
||||||
const weekDateKeys = new Set(weekDays.map(formatDateKey));
|
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))
|
weekDateKeys.has(String(attendance.date).slice(0, 10))
|
||||||
));
|
));
|
||||||
const times = weekAttendances.flatMap((attendance) => [
|
const times = weekAttendances.flatMap((attendance) => [
|
||||||
@ -246,8 +291,8 @@ const WeeklySummaryPage = ({ userId }) => {
|
|||||||
<div className="schedule-day-column" key={formatDateKey(day)}>
|
<div className="schedule-day-column" key={formatDateKey(day)}>
|
||||||
{hours.map((hour) => <span className="schedule-hour-line" key={hour} />)}
|
{hours.map((hour) => <span className="schedule-hour-line" key={hour} />)}
|
||||||
{getAttendanceSegments(getAttendancesForDay(day)).map(({ start, end, users }) => {
|
{getAttendanceSegments(getAttendancesForDay(day)).map(({ start, end, users }) => {
|
||||||
const startTime = `${String(Math.floor(start / 60)).padStart(2, '0')}:${String(start % 60).padStart(2, '0')}`;
|
const startTime = formatSegmentTime(start);
|
||||||
const endTime = `${String(Math.floor(end / 60)).padStart(2, '0')}:${String(end % 60).padStart(2, '0')}`;
|
const endTime = formatSegmentTime(end);
|
||||||
const segmentLabel = users.map(({ username }) => username).join(', ');
|
const segmentLabel = users.map(({ username }) => username).join(', ');
|
||||||
return (
|
return (
|
||||||
isAdmin ? (
|
isAdmin ? (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user