This commit is contained in:
Louis Mazin 2026-09-15 20:21:53 +02:00
parent 923fbf1fbc
commit 51188cb073
3 changed files with 51 additions and 79 deletions

View File

@ -121,8 +121,22 @@ class TimeEntryScanView(APIView):
end_time__isnull=True, end_time__isnull=True,
).exclude(date=now.date()).order_by('-date', '-start_time').first() ).exclude(date=now.date()).order_by('-date', '-start_time').first()
if previous_open_entry: if previous_open_entry:
last_date = previous_open_entry.date
previous_open_entry.end_time = datetime_time(23, 59, 59)
previous_open_entry.save(update_fields=['end_time', 'updated_at'])
current_date = last_date + timedelta(days=1)
while current_date < now.date():
TimeEntry.objects.create(
user=request.user,
date=current_date,
start_time=datetime_time.min,
end_time=datetime_time(23, 59, 59),
)
current_date += timedelta(days=1)
entry = TimeEntry.objects.create(user=request.user, date=now.date(), end_time=selected_time) entry = TimeEntry.objects.create(user=request.user, date=now.date(), end_time=selected_time)
message = 'Sortie enregistrée. L’entrée est restée sur la journée précédente.' message = 'Sortie enregistrée sur plusieurs jours.'
else: else:
entry = TimeEntry.objects.create(user=request.user, date=now.date(), end_time=selected_time) entry = TimeEntry.objects.create(user=request.user, date=now.date(), end_time=selected_time)
message = 'Sortie enregistrée sans entrée correspondante.' message = 'Sortie enregistrée sans entrée correspondante.'

View File

@ -4,12 +4,6 @@ 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);
@ -80,10 +74,24 @@ const TimeClockPage = () => {
end_time: newEntry.end_time, end_time: newEntry.end_time,
}); });
} else { } else {
await Promise.all([ const dates = [];
timeApi.createTimeEntry({ date: newEntry.date, start_time: newEntry.start_time }), const cursor = new Date(`${newEntry.date}T12:00:00`);
timeApi.createTimeEntry({ date: newEntry.end_date, end_time: newEntry.end_time }), const lastDate = new Date(`${newEntry.end_date}T12:00:00`);
]);
while (cursor <= lastDate) {
dates.push(cursor.toISOString().slice(0, 10));
cursor.setDate(cursor.getDate() + 1);
}
await Promise.all(dates.map((date, index) => {
if (index === 0) {
return timeApi.createTimeEntry({ date, start_time: newEntry.start_time });
}
if (index === dates.length - 1) {
return timeApi.createTimeEntry({ date, end_time: newEntry.end_time });
}
return timeApi.createTimeEntry({ date, start_time: '00:00', end_time: '23:59' });
}));
} }
setNewEntry({ setNewEntry({
date: new Date().toISOString().split('T')[0], date: new Date().toISOString().split('T')[0],
@ -280,46 +288,28 @@ const TimeClockPage = () => {
}; };
const getDisplayTimeEntries = () => { const getDisplayTimeEntries = () => {
const pairedEndIds = new Set();
const expanded = [];
const toMinutes = (time) => { const toMinutes = (time) => {
const [hours, minutes] = time.split(':').map(Number); const [hours, minutes] = time.split(':').map(Number);
return hours * 60 + minutes; return hours * 60 + minutes;
}; };
timeEntries.forEach((entry) => { return timeEntries.map((entry) => {
if (!entry.start_time && entry.end_time) return;
if (entry.start_time && !entry.end_time) { if (entry.start_time && !entry.end_time) {
const dateKey = String(entry.date).slice(0, 10); return {
const matchingEnd = timeEntries.find((candidate) => ( ...entry,
candidate.id !== entry.id && end_time: '24:00:00',
!candidate.start_time && duration: (1440 - toMinutes(entry.start_time)) / 60,
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;
}
} }
if (!entry.start_time && entry.end_time) {
expanded.push(entry); return {
...entry,
start_time: '00:00:00',
duration: toMinutes(entry.end_time) / 60,
};
}
return entry;
}); });
return expanded.filter((entry) => !pairedEndIds.has(entry.id));
}; };
const formatDate = (dateString) => { const formatDate = (dateString) => {

View File

@ -8,44 +8,12 @@ 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 expandOvernightAttendances = (entries) => {
const pairedEndIds = new Set(); return entries.map((entry) => {
const expanded = []; if (entry.start_time && !entry.end_time) return { ...entry, end_time: '24:00:00' };
if (!entry.start_time && entry.end_time) return { ...entry, start_time: '00:00:00' };
entries.forEach((entry) => { return 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 }) => {