test
This commit is contained in:
parent
ba76763c24
commit
a5b5c6def8
@ -17,8 +17,10 @@ import { Checkbox } from '@/components/ui/checkbox';
|
|||||||
import ConfirmDialog from '@/components/ConfirmDialog';
|
import ConfirmDialog from '@/components/ConfirmDialog';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { useAuth } from '../context/useAuth';
|
||||||
|
|
||||||
const AdminUsersPage = () => {
|
const AdminUsersPage = () => {
|
||||||
|
const { currentUser, logout } = useAuth();
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
@ -78,20 +80,12 @@ const AdminUsersPage = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDeleteUser = async (userId) => {
|
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 {
|
try {
|
||||||
await userApi.deleteUser(userId);
|
await userApi.deleteUser(userId);
|
||||||
setUsers(users.filter(user => user.id !== userId));
|
setUsers(users.filter(user => user.id !== userId));
|
||||||
|
if(userId===currentUser.id){
|
||||||
|
logout();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Erreur lors de la suppression de l\'utilisateur');
|
setError('Erreur lors de la suppression de l\'utilisateur');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@ -211,12 +211,15 @@ const TimePicker = ({
|
|||||||
const TimeClockPage = () => {
|
const TimeClockPage = () => {
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const qrControlsRef = useRef(null);
|
const qrControlsRef = useRef(null);
|
||||||
const { isAdmin } = useAuth();
|
const { isAdmin, user: authUser, currentUser } = useAuth();
|
||||||
const [scanMessage, setScanMessage] = useState('');
|
const [scanMessage, setScanMessage] = useState('');
|
||||||
const [scanConflict, setScanConflict] = useState(null);
|
const [scanConflict, setScanConflict] = useState(null);
|
||||||
const [conflictTime, setConflictTime] = useState(new Date().toTimeString().slice(0, 5));
|
const [conflictTime, setConflictTime] = useState(new Date().toTimeString().slice(0, 5));
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [timeEntries, setTimeEntries] = useState([]);
|
const [timeEntries, setTimeEntries] = useState([]);
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const [selectedUserId, setSelectedUserId] = useState('');
|
||||||
|
const [showAllUsers, setShowAllUsers] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isScannerDialogOpen, setIsScannerDialogOpen] = useState(false);
|
const [isScannerDialogOpen, setIsScannerDialogOpen] = useState(false);
|
||||||
const [cameraPermissionError, setCameraPermissionError] = 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 () => {
|
const loadTimeEntries = async () => {
|
||||||
try {
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
const entries = await timeApi.getTimeEntries();
|
const entries = await timeApi.getTimeEntries();
|
||||||
setTimeEntries(entries);
|
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) {
|
} catch (err) {
|
||||||
setError('Erreur lors du chargement des pointages');
|
setError('Erreur lors du chargement des pointages');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -246,7 +375,6 @@ const TimeClockPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load time entries when component mounts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTimeEntries();
|
loadTimeEntries();
|
||||||
}, []);
|
}, []);
|
||||||
@ -258,12 +386,20 @@ const TimeClockPage = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdmin && !showAllUsers && !selectedUserId) {
|
||||||
|
setError('Veuillez sélectionner un utilisateur.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (newEntry.date === newEntry.end_date) {
|
if (newEntry.date === newEntry.end_date) {
|
||||||
await timeApi.createTimeEntry({
|
await timeApi.createTimeEntry({
|
||||||
date: newEntry.date,
|
date: newEntry.date,
|
||||||
start_time: newEntry.start_time,
|
start_time: newEntry.start_time,
|
||||||
end_time: newEntry.end_time,
|
end_time: newEntry.end_time,
|
||||||
|
...(isAdmin && selectedUserId
|
||||||
|
? { user_id: selectedUserId }
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const dates = [];
|
const dates = [];
|
||||||
@ -277,12 +413,31 @@ const TimeClockPage = () => {
|
|||||||
|
|
||||||
await Promise.all(dates.map((date, index) => {
|
await Promise.all(dates.map((date, index) => {
|
||||||
if (index === 0) {
|
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) {
|
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({
|
setNewEntry({
|
||||||
@ -650,6 +805,16 @@ const TimeClockPage = () => {
|
|||||||
return `${hours}h${minutes}`;
|
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 (
|
return (
|
||||||
<Card className="flex h-full w-full">
|
<Card className="flex h-full w-full">
|
||||||
<CardHeader className="border-b">
|
<CardHeader className="border-b">
|
||||||
@ -712,10 +877,71 @@ const TimeClockPage = () => {
|
|||||||
{/* Time tracking section */}
|
{/* Time tracking section */}
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{isAdmin && (
|
{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">
|
<Card className="mb-4">
|
||||||
<CardHeader className="border-b">
|
<CardHeader className="border-b">
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{editingEntry ? 'Modifier le pointage' : 'Ajouter un pointage manuel'}
|
{editingEntry
|
||||||
|
? 'Modifier le pointage'
|
||||||
|
: `Ajouter un pointage manuel${selectedUser ? ` — ${getUserLabel(selectedUser)}` : ''}`}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -848,14 +1074,14 @@ const TimeClockPage = () => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p>Chargement des pointages...</p>
|
<p>Chargement des pointages...</p>
|
||||||
) : timeEntries.length === 0 ? (
|
) : displayedTimeEntries.length === 0 ? (
|
||||||
<p>Aucun pointage enregistré</p>
|
<p>Aucun pointage enregistré</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
{isAdmin && <TableHead>Nom</TableHead>}
|
{isAdmin && <TableHead>Utilisateur</TableHead>}
|
||||||
<TableHead>Date</TableHead>
|
<TableHead>Date</TableHead>
|
||||||
<TableHead>Entrée</TableHead>
|
<TableHead>Entrée</TableHead>
|
||||||
<TableHead>Sortie</TableHead>
|
<TableHead>Sortie</TableHead>
|
||||||
@ -864,9 +1090,16 @@ const TimeClockPage = () => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{getDisplayTimeEntries().map((entry) => (
|
{getDisplayTimeEntries()
|
||||||
|
.filter(
|
||||||
|
(entry) =>
|
||||||
|
!isAdmin ||
|
||||||
|
showAllUsers ||
|
||||||
|
getEntryUserId(entry) === String(selectedUserId),
|
||||||
|
)
|
||||||
|
.map((entry) => (
|
||||||
<TableRow key={entry.id}>
|
<TableRow key={entry.id}>
|
||||||
{isAdmin && <TableCell>{entry.first_name} {entry.last_name}</TableCell>}
|
{isAdmin && <TableCell>{entry.user}</TableCell>}
|
||||||
<TableCell>{formatDate(entry.date)}</TableCell>
|
<TableCell>{formatDate(entry.date)}</TableCell>
|
||||||
<TableCell>{formatTime(entry.start_time)}</TableCell>
|
<TableCell>{formatTime(entry.start_time)}</TableCell>
|
||||||
<TableCell>{formatTime(entry.end_time)}</TableCell>
|
<TableCell>{formatTime(entry.end_time)}</TableCell>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user