This commit is contained in:
Louis Mazin 2026-09-19 22:29:29 +02:00
parent ba76763c24
commit a5b5c6def8
2 changed files with 248 additions and 21 deletions

View File

@ -17,8 +17,10 @@ import { Checkbox } from '@/components/ui/checkbox';
import ConfirmDialog from '@/components/ConfirmDialog';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { useAuth } from '../context/useAuth';
const AdminUsersPage = () => {
const { currentUser, logout } = useAuth();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
@ -78,20 +80,12 @@ const AdminUsersPage = () => {
}, []);
const handleDeleteUser = async (userId) => {
// Remplacer window.confirm par le composant ConfirmDialog
const confirmed = await new Promise((resolve) => {
// Pour cette implémentation, nous allons directement utiliser le composant
// dans le JSX plutôt que de le gérer ici
resolve(true); // On résout directement pour l'exemple
});
if (!confirmed) {
return;
}
try {
await userApi.deleteUser(userId);
setUsers(users.filter(user => user.id !== userId));
if(userId===currentUser.id){
logout();
}
} catch (err) {
setError('Erreur lors de la suppression de l\'utilisateur');
console.error(err);

View File

@ -211,12 +211,15 @@ const TimePicker = ({
const TimeClockPage = () => {
const videoRef = useRef(null);
const qrControlsRef = useRef(null);
const { isAdmin } = useAuth();
const { isAdmin, user: authUser, currentUser } = useAuth();
const [scanMessage, setScanMessage] = useState('');
const [scanConflict, setScanConflict] = useState(null);
const [conflictTime, setConflictTime] = useState(new Date().toTimeString().slice(0, 5));
const [error, setError] = useState('');
const [timeEntries, setTimeEntries] = useState([]);
const [users, setUsers] = useState([]);
const [selectedUserId, setSelectedUserId] = useState('');
const [showAllUsers, setShowAllUsers] = useState(false);
const [loading, setLoading] = useState(true);
const [isScannerDialogOpen, setIsScannerDialogOpen] = useState(false);
const [cameraPermissionError, setCameraPermissionError] = useState(false);
@ -234,10 +237,136 @@ const TimeClockPage = () => {
});
const getUserId = (user) => {
if (!user) return '';
return String(
user.id ??
user.pk ??
user.user_id ??
user.user?.id ??
''
);
};
const getUserLabel = (user) => {
if (!user) return 'Utilisateur';
const firstName = user.first_name || user.firstName || '';
const lastName = user.last_name || user.lastName || '';
const fullName = `${firstName} ${lastName}`.trim();
return (
fullName ||
user.username ||
user.name ||
user.email ||
`Utilisateur #${getUserId(user)}`
);
};
const getEntryUserId = (entry) => {
return String(
entry.user_id ??
entry.userId ??
entry.user?.id ??
entry.user?.pk ??
''
);
};
const getCurrentUserId = () => {
return getUserId(authUser) || getUserId(currentUser);
};
const loadTimeEntries = async () => {
try {
setLoading(true);
const entries = await timeApi.getTimeEntries();
setTimeEntries(entries);
if (!isAdmin) return;
// Si l'API expose une liste d'utilisateurs, on l'utilise.
// Sinon, on reconstruit une liste à partir des pointages retournés.
let availableUsers = [];
if (typeof timeApi.getUsers === 'function') {
try {
const apiUsers = await timeApi.getUsers();
availableUsers = Array.isArray(apiUsers) ? apiUsers : [];
} catch (usersError) {
console.warn(
'Impossible de charger la liste des utilisateurs. Fallback sur les pointages.',
usersError,
);
}
}
if (!availableUsers.length) {
const usersById = new Map();
entries.forEach((entry) => {
const id = getEntryUserId(entry);
if (!id || usersById.has(id)) return;
const entryUser =
typeof entry.user === 'object' && entry.user
? entry.user
: {
id,
username: entry.username,
name: entry.user,
};
usersById.set(id, entryUser);
});
availableUsers = [...usersById.values()];
}
const currentUserId = getCurrentUserId();
if (
currentUserId &&
!availableUsers.some(
(availableUser) => getUserId(availableUser) === currentUserId,
)
) {
const currentUser = authUser || currentUser;
if (currentUser) {
availableUsers.unshift(currentUser);
}
}
availableUsers = availableUsers
.filter((availableUser) => getUserId(availableUser))
.sort((first, second) =>
getUserLabel(first).localeCompare(getUserLabel(second), 'fr'),
);
setUsers(availableUsers);
setSelectedUserId((currentSelectedId) => {
if (
currentSelectedId &&
availableUsers.some(
(availableUser) =>
getUserId(availableUser) === String(currentSelectedId),
)
) {
return currentSelectedId;
}
return (
currentUserId ||
getUserId(availableUsers[0]) ||
''
);
});
} catch (err) {
setError('Erreur lors du chargement des pointages');
console.error(err);
@ -246,7 +375,6 @@ const TimeClockPage = () => {
}
};
// Load time entries when component mounts
useEffect(() => {
loadTimeEntries();
}, []);
@ -258,12 +386,20 @@ const TimeClockPage = () => {
return;
}
if (isAdmin && !showAllUsers && !selectedUserId) {
setError('Veuillez sélectionner un utilisateur.');
return;
}
try {
if (newEntry.date === newEntry.end_date) {
await timeApi.createTimeEntry({
date: newEntry.date,
start_time: newEntry.start_time,
end_time: newEntry.end_time,
...(isAdmin && selectedUserId
? { user_id: selectedUserId }
: {}),
});
} else {
const dates = [];
@ -277,12 +413,31 @@ const TimeClockPage = () => {
await Promise.all(dates.map((date, index) => {
if (index === 0) {
return timeApi.createTimeEntry({ date, start_time: newEntry.start_time });
return timeApi.createTimeEntry({
date,
start_time: newEntry.start_time,
...(isAdmin && selectedUserId
? { user_id: selectedUserId }
: {}),
});
}
if (index === dates.length - 1) {
return timeApi.createTimeEntry({ date, end_time: newEntry.end_time });
return timeApi.createTimeEntry({
date,
end_time: newEntry.end_time,
...(isAdmin && selectedUserId
? { user_id: selectedUserId }
: {}),
});
}
return timeApi.createTimeEntry({ date, start_time: '00:00', end_time: '23:59' });
return timeApi.createTimeEntry({
date,
start_time: '00:00',
end_time: '23:59',
...(isAdmin && selectedUserId
? { user_id: selectedUserId }
: {}),
});
}));
}
setNewEntry({
@ -650,6 +805,16 @@ const TimeClockPage = () => {
return `${hours}h${minutes}`;
};
const displayedTimeEntries = isAdmin && !showAllUsers && selectedUserId
? timeEntries.filter(
(entry) => getEntryUserId(entry) === String(selectedUserId),
)
: timeEntries;
const selectedUser = users.find(
(user) => getUserId(user) === String(selectedUserId),
);
return (
<Card className="flex h-full w-full">
<CardHeader className="border-b">
@ -712,10 +877,71 @@ const TimeClockPage = () => {
{/* Time tracking section */}
<div className="w-full">
{isAdmin && (
<Card className="mb-4">
<CardHeader className="border-b">
<CardTitle>Utilisateur</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-col gap-3 md:flex-row md:items-end">
<div className="min-w-0 flex-1">
<Label htmlFor="selected-user">
Voir les pointages de
</Label>
<select
id="selected-user"
value={selectedUserId}
disabled={showAllUsers}
onChange={(event) => {
setSelectedUserId(event.target.value);
setEditingEntry(null);
setError('');
}}
className="mt-2 flex h-10 w-full 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>
))
)}
</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);
setEditingEntry(null);
setError('');
}}
className="size-4 accent-primary"
/>
<span>Voir tout le monde</span>
</label>
</div>
{showAllUsers && (
<p className="text-sm text-muted-foreground">
La sélection d’un utilisateur et le pointage manuel sont
désactivés lorsque tous les utilisateurs sont affichés.
</p>
)}
</CardContent>
</Card>
)}
{isAdmin && !showAllUsers && (
<Card className="mb-4">
<CardHeader className="border-b">
<CardTitle>
{editingEntry ? 'Modifier le pointage' : 'Ajouter un pointage manuel'}
{editingEntry
? 'Modifier le pointage'
: `Ajouter un pointage manuel${selectedUser ? ` — ${getUserLabel(selectedUser)}` : ''}`}
</CardTitle>
</CardHeader>
<CardContent>
@ -848,14 +1074,14 @@ const TimeClockPage = () => {
<CardContent>
{loading ? (
<p>Chargement des pointages...</p>
) : timeEntries.length === 0 ? (
) : displayedTimeEntries.length === 0 ? (
<p>Aucun pointage enregistré</p>
) : (
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
{isAdmin && <TableHead>Nom</TableHead>}
{isAdmin && <TableHead>Utilisateur</TableHead>}
<TableHead>Date</TableHead>
<TableHead>Entrée</TableHead>
<TableHead>Sortie</TableHead>
@ -864,9 +1090,16 @@ const TimeClockPage = () => {
</TableRow>
</TableHeader>
<TableBody>
{getDisplayTimeEntries().map((entry) => (
{getDisplayTimeEntries()
.filter(
(entry) =>
!isAdmin ||
showAllUsers ||
getEntryUserId(entry) === String(selectedUserId),
)
.map((entry) => (
<TableRow key={entry.id}>
{isAdmin && <TableCell>{entry.first_name} {entry.last_name}</TableCell>}
{isAdmin && <TableCell>{entry.user}</TableCell>}
<TableCell>{formatDate(entry.date)}</TableCell>
<TableCell>{formatTime(entry.start_time)}</TableCell>
<TableCell>{formatTime(entry.end_time)}</TableCell>