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,
).exclude(date=now.date()).order_by('-date', '-start_time').first()
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)
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:
entry = TimeEntry.objects.create(user=request.user, date=now.date(), end_time=selected_time)
message = 'Sortie enregistrée sans entrée correspondante.'

View File

@ -4,12 +4,6 @@ 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);
@ -80,10 +74,24 @@ const TimeClockPage = () => {
end_time: newEntry.end_time,
});
} else {
await Promise.all([
timeApi.createTimeEntry({ date: newEntry.date, start_time: newEntry.start_time }),
timeApi.createTimeEntry({ date: newEntry.end_date, end_time: newEntry.end_time }),
]);
const dates = [];
const cursor = new Date(`${newEntry.date}T12:00:00`);
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({
date: new Date().toISOString().split('T')[0],
@ -280,46 +288,28 @@ const TimeClockPage = () => {
};
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;
return timeEntries.map((entry) => {
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;
}
return {
...entry,
end_time: '24:00:00',
duration: (1440 - toMinutes(entry.start_time)) / 60,
};
}
expanded.push(entry);
if (!entry.start_time && entry.end_time) {
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) => {

View File

@ -8,44 +8,12 @@ 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 entries.map((entry) => {
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' };
return entry;
});
return expanded.filter((entry) => !pairedEndIds.has(entry.id));
};
const WeeklySummaryPage = ({ userId }) => {