This commit is contained in:
Louis Mazin 2026-09-20 02:17:25 +02:00
parent 49bbf77c04
commit addd13efae

View File

@ -84,13 +84,6 @@ const WeeklySummaryPage = ({ userId }) => {
const [isMobile, setIsMobile] = useState(false);
/*
* Largeur réelle disponible du planning mobile.
*
* Important :
* on ne se base pas directement sur window.innerWidth,
* car le CardContent possède ses propres marges/paddings.
*/
const [mobileContainerWidth, setMobileContainerWidth] =
useState(0);
@ -426,60 +419,95 @@ const WeeklySummaryPage = ({ userId }) => {
const scheduleTable =
scheduleTableRef.current;
const main =
document.querySelector('main');
const footer =
document.querySelector('footer');
if (!scheduleTable) {
return;
}
/*
* Largeur réelle visible du planning.
*
* On utilise clientWidth et non window.innerWidth
* afin de tenir compte du CardContent, des paddings,
* des bordures, etc.
* Largeur réellement disponible.
*/
setMobileContainerWidth(
scheduleTable.clientWidth,
);
if (
!main ||
!footer
) {
/*
* Sur mobile, on ne force pas la hauteur du planning
* à rentrer dans l'écran.
*
* Le planning doit pouvoir être scrollé verticalement.
*/
if (window.innerWidth < 768) {
return;
}
const mainStyles =
window.getComputedStyle(main);
const cardContent =
scheduleTable.closest(
'[data-slot="card-content"]',
);
const bottomPadding =
parseFloat(
mainStyles.paddingBottom,
) || 0;
const footer =
document.querySelector('footer');
const footerHeight =
footer.getBoundingClientRect().height;
if (!cardContent) {
return;
}
/*
* Position réelle du planning.
*/
const tableTop =
scheduleTable.getBoundingClientRect().top;
const remainingHeight =
window.innerHeight -
tableTop -
footerHeight -
bottomPadding -
SCHEDULE_HEADER_HEIGHT;
/*
* Limite basse réelle disponible.
*
* Cela tient compte :
* - du CardContent
* - de son padding
* - du footer
* - de la hauteur de la fenêtre
*/
const contentRect =
cardContent.getBoundingClientRect();
setAvailableScheduleHeight(
const footerTop = footer
? footer.getBoundingClientRect().top
: window.innerHeight;
const availableBottom =
Math.min(
contentRect.bottom,
footerTop,
window.innerHeight,
);
/*
* Petite marge de sécurité pour éviter
* les scrollbars de quelques pixels.
*/
const SAFETY_MARGIN = 4;
const totalAvailableHeight =
availableBottom -
tableTop -
SAFETY_MARGIN;
const nextTotalHeight =
Math.max(
SCHEDULE_HEADER_HEIGHT +
MIN_SCHEDULE_HEIGHT,
totalAvailableHeight,
);
const nextScheduleHeight =
Math.max(
MIN_SCHEDULE_HEIGHT,
remainingHeight,
),
nextTotalHeight -
SCHEDULE_HEADER_HEIGHT,
);
setAvailableScheduleHeight(
nextScheduleHeight,
);
};
@ -873,9 +901,6 @@ const WeeklySummaryPage = ({ userId }) => {
/*
* ------------------------------------------------------------
* Utilisateurs actifs sur une période
*
* Desktop -> toute la semaine
* Mobile -> journée sélectionnée
* ------------------------------------------------------------
*/
@ -1010,10 +1035,23 @@ const WeeklySummaryPage = ({ userId }) => {
const hourCount =
endHour - startHour;
const scheduleHeight = Math.max(
MIN_SCHEDULE_HEIGHT,
availableScheduleHeight,
);
/*
* Desktop :
* hauteur calculée pour tenir exactement dans le Card.
*
* Mobile :
* on conserve une hauteur confortable et le conteneur
* devient scrollable verticalement.
*/
const scheduleHeight = isMobile
? Math.max(
MIN_SCHEDULE_HEIGHT,
availableScheduleHeight,
)
: Math.max(
MIN_SCHEDULE_HEIGHT,
availableScheduleHeight,
);
const totalPlanningHeight =
SCHEDULE_HEADER_HEIGHT +
@ -1044,9 +1082,6 @@ const WeeklySummaryPage = ({ userId }) => {
/*
* ------------------------------------------------------------
* Utilisateurs réellement affichés
*
* Desktop : actifs sur la semaine
* Mobile : actifs sur le jour
* ------------------------------------------------------------
*/
@ -1071,17 +1106,6 @@ const WeeklySummaryPage = ({ userId }) => {
/*
* ------------------------------------------------------------
* Largeur mobile
*
* La zone utilisateurs prend maintenant toute la largeur
* réellement disponible dans le conteneur.
*
* On garde cependant une largeur minimale correspondant :
* - à la largeur minimale des colonnes utilisateurs
* - à l'ancien minimum de contenu
*
* Si plusieurs utilisateurs dépassent cette largeur,
* le planning peut naturellement devenir plus large et
* déclencher le scroll horizontal.
* ------------------------------------------------------------
*/
@ -1137,13 +1161,13 @@ const WeeklySummaryPage = ({ userId }) => {
return (
<Card
className={`flex h-full min-h-0 w-full weekly-schedule page-layout${
className={`flex h-full min-h-0 w-full overflow-hidden weekly-schedule page-layout${
isAdmin
? ' multi-user-schedule'
: ''
}`}
>
<CardHeader className="border-b">
<CardHeader className="shrink-0 border-b">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="min-w-0">
<CardTitle className="flex items-center gap-2">
@ -1264,77 +1288,76 @@ const WeeklySummaryPage = ({ userId }) => {
</div>
</CardHeader>
<CardContent>
<CardContent className="min-h-0 flex-1">
{/* Sélection utilisateur */}
{isAdmin && (
<div className="flex flex-col gap-3 pb-4 md:flex-row md:items-center">
<div className="flex min-w-0 flex-1 items-center gap-2">
<label
htmlFor="weekly-selected-user"
className="shrink-0 text-sm font-medium"
>
Voir le récap de :
</label>
<div className="flex flex-col gap-3 pb-4 md:flex-row md:items-center">
<div className="flex min-w-0 flex-1 items-center gap-2">
<label
htmlFor="weekly-selected-user"
className="shrink-0 text-sm font-medium"
>
Voir le récap de :
</label>
<select
id="weekly-selected-user"
value={selectedUserId}
disabled={showAllUsers}
onChange={(event) => {
setSelectedUserId(
event.target.value,
);
<select
id="weekly-selected-user"
value={selectedUserId}
disabled={showAllUsers}
onChange={(event) => {
setSelectedUserId(
event.target.value,
);
setError('');
}}
className="h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
>
{users.length ===
0 ? (
<option value="">
Aucun utilisateur
disponible
setError('');
}}
className="h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
>
{users.length === 0 ? (
<option value="">
Aucun utilisateur
disponible
</option>
) : (
users.map(
(user) => (
<option
key={getUserId(
user,
)}
value={getUserId(
user,
)}
>
{getUserLabel(
user,
)}
</option>
) : (
users.map(
(user) => (
<option
key={getUserId(
user,
)}
value={getUserId(
user,
)}
>
{getUserLabel(
user,
)}
</option>
),
)
)}
</select>
</div>
),
)
)}
</select>
</div>
<label className="flex h-10 shrink-0 cursor-pointer items-center gap-2 rounded-md border px-3 text-sm">
<input
type="checkbox"
checked={showAllUsers}
onChange={(event) => {
setShowAllUsers(
event.target.checked,
);
<label className="flex h-10 shrink-0 cursor-pointer items-center gap-2 rounded-md border px-3 text-sm">
<input
type="checkbox"
checked={showAllUsers}
onChange={(event) => {
setShowAllUsers(
event.target.checked,
);
setError('');
}}
className="size-4 accent-primary"
/>
setError('');
}}
className="size-4 accent-primary"
/>
<span>
Voir tout le monde
</span>
</label>
</div>
<span>
Voir tout le monde
</span>
</label>
</div>
)}
{/* Légende */}
@ -1381,9 +1404,20 @@ const WeeklySummaryPage = ({ userId }) => {
<div
ref={scheduleTableRef}
className="relative overflow-x-auto overflow-y-auto rounded-lg border"
className={`relative rounded-lg border ${
isMobile
? 'overflow-auto'
: 'overflow-x-auto overflow-y-hidden'
}`}
style={{
height: `${totalPlanningHeight}px`,
maxHeight: isMobile
? 'none'
: `${totalPlanningHeight}px`,
WebkitOverflowScrolling:
'touch',
touchAction:
'pan-x pan-y',
}}
>
{/* ====================================================
@ -1397,6 +1431,7 @@ const WeeklySummaryPage = ({ userId }) => {
width: `${mobilePlanningWidth}px`,
minWidth: `${mobilePlanningWidth}px`,
height: `${totalPlanningHeight}px`,
minHeight: `${totalPlanningHeight}px`,
gridTemplateRows: `${SCHEDULE_HEADER_HEIGHT}px ${scheduleHeight}px`,
}}
>
@ -1479,11 +1514,7 @@ const WeeklySummaryPage = ({ userId }) => {
),
)}
{/* ==================================================
ADMIN + TOUS LES UTILISATEURS
1 utilisateur actif = 1 colonne
================================================== */}
{/* Tous les utilisateurs */}
{isAdmin &&
showAllUsers ? (
<div
@ -1515,11 +1546,9 @@ const WeeklySummaryPage = ({ userId }) => {
user,
)}
className="relative border-r border-border/30 last:border-r-0"
title={
getUserLabel(
user,
)
}
title={getUserLabel(
user,
)}
>
{schedules.map(
({
@ -1563,10 +1592,6 @@ const WeeklySummaryPage = ({ userId }) => {
)}
</div>
) : (
/*
* Utilisateur sélectionné /
* utilisateur normal.
*/
getSelectedUserAttendances(
getAttendancesForDay(
weekDays[
@ -1767,15 +1792,7 @@ const WeeklySummaryPage = ({ userId }) => {
),
)}
{/* ==================================================
ADMIN : TOUS LES UTILISATEURS
1 utilisateur ayant pointé dans la semaine
= 1 colonne
Chaque pointage est affiché séparément.
================================================== */}
{/* Tous les utilisateurs */}
{isAdmin &&
showAllUsers ? (
<div
@ -1851,9 +1868,6 @@ const WeeklySummaryPage = ({ userId }) => {
)}
</div>
) : (
/*
* Utilisateur sélectionné
*/
getSelectedUserAttendances(
dayAttendances,
)