Files
projet_gestion_commande/frontend-prep/src/pages/CabineUserManager/UserManagement.tsx
T

877 lines
27 KiB
TypeScript

import { useState, useEffect, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import {
User,
Shield,
Truck,
Store,
Search,
XCircle,
Phone,
TrendingUp,
AlertCircle,
Filter,
Eye,
Leaf,
Wind,
} from "lucide-react";
import "./UserManagement.css";
import SidebarCabine from "../../components/SidebarCabine";
import { getAllUsers, getAllClients } from "../../api/api_admin";
import { isCabineAuthenticated } from "../../api/api_cabine"; // ⭐ AJOUT
import type { AdminResponse } from "../../api/api_admin_types";
import type { ClientResponse } from "../../api/api_admin";
// ============================================
// TYPES & INTERFACES
// ============================================
interface UserStats {
totalUsers: number;
totalCabines: number;
totalLivreurs: number;
}
type UserRole = "admin" | "cabine" | "livreur" | "client";
type RoleFilter = "all" | "cabine" | "livreur";
interface RoleConfig {
color: string;
label: string;
icon: React.ComponentType<{ size: number }>;
}
// ============================================
// CONSTANTS
// ============================================
const ROLE_CONFIG: Record<UserRole, RoleConfig> = {
admin: { color: "red", label: "Administrateur", icon: Shield },
cabine: { color: "purple", label: "Cabine", icon: Store },
livreur: { color: "blue", label: "Livreur", icon: Truck },
client: { color: "green", label: "Client", icon: User },
};
// ============================================
// MAIN COMPONENT
// ============================================
function UsersManagement() {
const navigate = useNavigate();
// État des utilisateurs
const [users, setUsers] = useState<AdminResponse[]>([]);
const [filteredUsers, setFilteredUsers] = useState<AdminResponse[]>([]);
const [stats, setStats] = useState<UserStats>({
totalUsers: 0,
totalCabines: 0,
totalLivreurs: 0,
});
// État des clients
const [clients, setClients] = useState<ClientResponse[]>([]);
const [filteredClients, setFilteredClients] = useState<ClientResponse[]>(
[],
);
const [totalClients, setTotalClients] = useState(0);
// État des filtres et recherche
const [searchTerm, setSearchTerm] = useState("");
const [clientSearchTerm, setClientSearchTerm] = useState("");
const [roleFilter, setRoleFilter] = useState<RoleFilter>("all");
// État du modal
const [selectedClient, setSelectedClient] = useState<ClientResponse | null>(
null,
);
const [showClientModal, setShowClientModal] = useState(false);
// État de chargement
const [loading, setLoading] = useState(true);
// ============================================
// 🔐 VÉRIFICATION INITIALE DE L'AUTHENTIFICATION
// ============================================
useEffect(() => {
const checkAuth = () => {
if (!isCabineAuthenticated()) {
console.log(
"❌ [UserManagement] Cabine non authentifié, redirection vers /login-cabine/cabine",
);
navigate("/login-cabine/cabine", { replace: true });
}
};
checkAuth();
}, [navigate]);
// ============================================
// 🔐 VÉRIFICATION CONTINUE (toutes les 5 secondes)
// ============================================
useEffect(() => {
const authInterval = setInterval(() => {
if (!isCabineAuthenticated()) {
console.log(
"❌ [UserManagement] Session cabine expirée, redirection vers /login-cabine/cabine",
);
navigate("/login-cabine/cabine", { replace: true });
}
}, 5000);
return () => clearInterval(authInterval);
}, [navigate]);
// ============================================
// FETCH DATA
// ============================================
const fetchUsers = useCallback(async () => {
// ✅ Vérifier l'auth avant de charger
if (!isCabineAuthenticated()) {
console.log("❌ [fetchUsers] Cabine non authentifié");
navigate("/login-cabine/cabine", { replace: true });
return;
}
try {
console.log("👥 [USERS_MANAGEMENT] Chargement des utilisateurs...");
setLoading(true);
const usersData = await getAllUsers();
console.log(
"✅ [USERS_MANAGEMENT] Utilisateurs récupérés:",
usersData,
);
// Exclure les admins de la liste
const filteredUsersData = usersData.filter(
(u) => u.role !== "admin",
);
setUsers(filteredUsersData);
// Calculer les statistiques
const newStats: UserStats = {
totalUsers: filteredUsersData.length,
totalCabines: filteredUsersData.filter(
(u) => u.role === "cabine",
).length,
totalLivreurs: filteredUsersData.filter(
(u) => u.role === "livreur",
).length,
};
setStats(newStats);
console.log("📊 [USERS_MANAGEMENT] Stats:", newStats);
} catch (error) {
console.error("❌ [USERS_MANAGEMENT] Erreur chargement:", error);
if (error instanceof Error && error.message.includes("401")) {
console.log("🔓 [UserManagement] Token invalide");
sessionStorage.removeItem("admin_token");
sessionStorage.removeItem("admin_username");
navigate("/login-cabine/cabine", { replace: true });
return;
}
alert("Erreur lors du chargement des utilisateurs");
} finally {
setLoading(false);
}
}, [navigate]);
const fetchClients = useCallback(async () => {
// ✅ Vérifier l'auth avant de charger
if (!isCabineAuthenticated()) {
console.log("❌ [fetchClients] Cabine non authentifié");
navigate("/login-cabine/cabine", { replace: true });
return;
}
try {
console.log("👥 [CLIENTS_MANAGEMENT] Chargement des clients...");
setLoading(true);
const clientData = await getAllClients();
console.log(
"✅ [CLIENTS_MANAGEMENT] Clients récupérés:",
clientData,
);
if (clientData && clientData.length > 0) {
const firstClient = clientData[0];
console.log("🔍 [DEBUG] Premier client:", {
username: firstClient.username,
points_weed: firstClient.point,
points_zipette: firstClient.points_zipette,
});
}
setClients(clientData);
setTotalClients(clientData.length);
} catch (error) {
console.error("❌ [CLIENTS_MANAGEMENT] Erreur:", error);
if (error instanceof Error && error.message.includes("401")) {
console.log("🔓 [UserManagement] Token invalide");
sessionStorage.removeItem("admin_token");
sessionStorage.removeItem("admin_username");
navigate("/login-cabine/cabine", { replace: true });
return;
}
alert("Erreur lors du chargement des clients");
} finally {
setLoading(false);
}
}, [navigate]);
// ============================================
// FILTERS
// ============================================
const filterUsers = useCallback(() => {
let filtered = [...users];
if (searchTerm) {
filtered = filtered.filter((user) =>
user.username.toLowerCase().includes(searchTerm.toLowerCase()),
);
}
if (roleFilter !== "all") {
filtered = filtered.filter((user) => user.role === roleFilter);
}
setFilteredUsers(filtered);
}, [users, searchTerm, roleFilter]);
const filterClients = useCallback(() => {
let filtered = [...clients];
if (clientSearchTerm) {
const searchLower = clientSearchTerm.toLowerCase();
filtered = filtered.filter(
(client) =>
client.nom.toLowerCase().includes(searchLower) ||
client.prenom.toLowerCase().includes(searchLower) ||
client.username.toLowerCase().includes(searchLower) ||
client.telephone.includes(clientSearchTerm),
);
}
setFilteredClients(filtered);
}, [clients, clientSearchTerm]);
// ============================================
// HANDLERS
// ============================================
const handleViewClient = useCallback((client: ClientResponse) => {
setSelectedClient(client);
setShowClientModal(true);
}, []);
const handleCloseModal = useCallback(() => {
setShowClientModal(false);
setSelectedClient(null);
}, []);
const handleRoleFilterChange = useCallback((filter: RoleFilter) => {
setRoleFilter(filter);
}, []);
// ============================================
// EFFECTS
// ============================================
useEffect(() => {
fetchUsers();
fetchClients();
}, [fetchUsers, fetchClients]);
useEffect(() => {
filterUsers();
}, [filterUsers]);
useEffect(() => {
filterClients();
}, [filterClients]);
// ============================================
// RENDER HELPERS
// ============================================
const getRoleConfig = (role: string): RoleConfig => {
return ROLE_CONFIG[role as UserRole] || ROLE_CONFIG.client;
};
const getRoleIcon = (role: string): React.ReactElement => {
const config = getRoleConfig(role);
const IconComponent = config.icon;
return <IconComponent size={18} />;
};
// ============================================
// LOADING STATE
// ============================================
if (loading) {
return (
<div className="users-management-container">
<div className="loading-users">
<p>Chargement des utilisateurs...</p>
</div>
</div>
);
}
// ============================================
// MAIN RENDER
// ============================================
return (
<>
<SidebarCabine />
<div className="users-management-container">
<Header />
<StatsGrid stats={stats} totalClients={totalClients} />
<FiltersSection
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
roleFilter={roleFilter}
onRoleFilterChange={handleRoleFilterChange}
stats={stats}
usersCount={users.length}
/>
<UsersList
filteredUsers={filteredUsers}
getRoleIcon={getRoleIcon}
getRoleConfig={getRoleConfig}
/>
<ClientsSection
clients={clients}
filteredClients={filteredClients}
clientSearchTerm={clientSearchTerm}
setClientSearchTerm={setClientSearchTerm}
onViewClient={handleViewClient}
/>
{showClientModal && selectedClient && (
<ClientModal
client={selectedClient}
onClose={handleCloseModal}
/>
)}
</div>
</>
);
}
// ============================================
// SUB-COMPONENTS (identiques à l'original)
// ============================================
function Header() {
return (
<div className="dashboard-header">
<h1>Gestion des Utilisateurs</h1>
<p className="dashboard-subtitle">
Visualiser et gérer tous les comptes système
</p>
</div>
);
}
interface StatsGridProps {
stats: UserStats;
totalClients: number;
}
function StatsGrid({ stats, totalClients }: StatsGridProps) {
const statsCards = [
{
icon: User,
color: "blue",
label: "Total Utilisateurs",
value: stats.totalUsers,
},
{
icon: Store,
color: "purple",
label: "Cabines",
value: stats.totalCabines,
},
{
icon: Truck,
color: "blue",
label: "Livreurs",
value: stats.totalLivreurs,
},
{
icon: User,
color: "green",
label: "Clients",
value: totalClients,
},
];
return (
<div className="stats-grid">
{statsCards.map((card, index) => (
<StatCard key={index} {...card} />
))}
</div>
);
}
interface StatCardProps {
icon: React.ComponentType<{ size: number }>;
color: string;
label: string;
value: number;
}
function StatCard({ icon: Icon, color, label, value }: StatCardProps) {
return (
<div className="stat-card">
<div className={`stat-icon ${color}`}>
<Icon size={24} />
</div>
<div className="stat-content">
<p className="stat-label">{label}</p>
<h3 className="stat-value">{value}</h3>
</div>
</div>
);
}
interface FiltersSectionProps {
searchTerm: string;
setSearchTerm: (term: string) => void;
roleFilter: RoleFilter;
onRoleFilterChange: (filter: RoleFilter) => void;
stats: UserStats;
usersCount: number;
}
function FiltersSection({
searchTerm,
setSearchTerm,
roleFilter,
onRoleFilterChange,
stats,
usersCount,
}: FiltersSectionProps) {
const filters = [
{
value: "all" as RoleFilter,
icon: Filter,
label: "Tous",
count: usersCount,
},
{
value: "cabine" as RoleFilter,
icon: Store,
label: "Cabines",
count: stats.totalCabines,
},
{
value: "livreur" as RoleFilter,
icon: Truck,
label: "Livreurs",
count: stats.totalLivreurs,
},
];
return (
<div className="filters-section">
<div className="search-bar">
<Search size={20} />
<input
type="text"
placeholder="Rechercher par username..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="role-filters">
{filters.map((filter) => {
const IconComponent = filter.icon;
return (
<button
key={filter.value}
className={`filter-btn ${roleFilter === filter.value ? "active" : ""}`}
onClick={() => onRoleFilterChange(filter.value)}
>
<IconComponent size={18} />
{filter.label} ({filter.count})
</button>
);
})}
</div>
</div>
);
}
interface UsersListProps {
filteredUsers: AdminResponse[];
getRoleIcon: (role: string) => React.ReactElement;
getRoleConfig: (role: string) => RoleConfig;
}
function UsersList({
filteredUsers,
getRoleIcon,
getRoleConfig,
}: UsersListProps) {
return (
<div className="users-section">
<h2>
<User size={24} />
Utilisateurs Système ({filteredUsers.length})
</h2>
<div className="users-list">
{filteredUsers.length === 0 ? (
<EmptyState
icon={User}
title="Aucun utilisateur trouvé"
message="Essayez de modifier vos filtres de recherche"
/>
) : (
filteredUsers.map((user) => (
<UserCard
key={user.id}
user={user}
getRoleIcon={getRoleIcon}
getRoleConfig={getRoleConfig}
/>
))
)}
</div>
</div>
);
}
interface UserCardProps {
user: AdminResponse;
getRoleIcon: (role: string) => React.ReactElement;
getRoleConfig: (role: string) => RoleConfig;
}
function UserCard({ user, getRoleIcon, getRoleConfig }: UserCardProps) {
const roleConfig = getRoleConfig(user.role);
return (
<div className="user-card">
<div className="user-header">
<div className="user-info-main">
<div className="user-avatar">{getRoleIcon(user.role)}</div>
<div className="user-details">
<strong className="user-username">
{user.username}
</strong>
<span className="user-id">ID: {user.id}</span>
</div>
</div>
<div className={`user-role-badge ${roleConfig.color}`}>
{getRoleIcon(user.role)}
<span>{roleConfig.label}</span>
</div>
</div>
</div>
);
}
interface ClientsSectionProps {
clients: ClientResponse[];
filteredClients: ClientResponse[];
clientSearchTerm: string;
setClientSearchTerm: (term: string) => void;
onViewClient: (client: ClientResponse) => void;
}
function ClientsSection({
clients,
filteredClients,
clientSearchTerm,
setClientSearchTerm,
onViewClient,
}: ClientsSectionProps) {
return (
<div className="users-section clients-section">
<div className="section-header">
<h2>
<User size={24} />
Clients Enregistrés ({clients.length})
</h2>
<div className="search-bar compact">
<Search size={18} />
<input
type="text"
placeholder="Rechercher un client..."
value={clientSearchTerm}
onChange={(e) => setClientSearchTerm(e.target.value)}
/>
</div>
</div>
<div className="users-list">
{filteredClients.length === 0 ? (
<EmptyState
icon={User}
title="Aucun client trouvé"
message={
clientSearchTerm
? "Essayez de modifier votre recherche"
: "Les clients s'afficheront ici une fois inscrits"
}
/>
) : (
filteredClients.map((client) => (
<ClientCard
key={client.id}
client={client}
onViewClient={onViewClient}
/>
))
)}
</div>
</div>
);
}
interface ClientCardProps {
client: ClientResponse;
onViewClient: (client: ClientResponse) => void;
}
function ClientCard({ client, onViewClient }: ClientCardProps) {
return (
<div className="user-card client-card">
<div className="user-header">
<div className="user-info-main">
<div className="user-avatar client">
<User size={18} />
</div>
<div className="user-details">
<strong className="user-username">
{client.prenom} {client.nom}
</strong>
<span className="user-id">@{client.username}</span>
</div>
</div>
<div className="user-role-badge green">
<User size={18} />
<span>Client</span>
</div>
</div>
<div className="client-info-grid">
<InfoItem icon={Phone} text={client.telephone} />
<InfoItem
icon={TrendingUp}
text={`${client.command} commande${client.command > 1 ? "s" : ""}`}
/>
<InfoItem icon={Leaf} text={`${client.point || 0} pts Weed`} />
<InfoItem
icon={Wind}
text={`${client.points_zipette || 0} pts Zipette`}
/>
<InfoItem
icon={AlertCircle}
text={`${client.amende}€ d'amendes`}
/>
</div>
<div className="user-actions">
<button
className="btn-view"
onClick={() => onViewClient(client)}
>
<Eye size={18} />
Voir détails
</button>
</div>
</div>
);
}
interface InfoItemProps {
icon: React.ComponentType<{ size: number }>;
text: string;
}
function InfoItem({ icon: Icon, text }: InfoItemProps) {
return (
<div className="info-item">
<Icon size={16} />
<span>{text}</span>
</div>
);
}
interface ClientModalProps {
client: ClientResponse;
onClose: () => void;
}
function ClientModal({ client, onClose }: ClientModalProps) {
const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget) {
onClose();
}
};
return (
<div className="modal-overlay" onClick={handleBackdropClick}>
<div
className="edit-modal-content client-modal"
onClick={(e) => e.stopPropagation()}
>
<div className="edit-modal-header">
<div className="modal-header-title">
<User size={24} />
<h3>Détails du Client</h3>
</div>
<button className="modal-close-btn" onClick={onClose}>
<XCircle size={20} />
</button>
</div>
<div className="edit-modal-body">
<DetailSection title="Informations personnelles">
<DetailItem
icon={User}
label="Prénom"
value={client.prenom}
/>
<DetailItem
icon={User}
label="Nom"
value={client.nom}
/>
<DetailItem
icon={Shield}
label="Username"
value={`@${client.username}`}
/>
<DetailItem
icon={Phone}
label="Téléphone"
value={client.telephone}
/>
</DetailSection>
<DetailSection title="Statistiques">
<DetailItem
icon={TrendingUp}
label="Commandes"
value={`${client.command} commande${client.command > 1 ? "s" : ""}`}
/>
<DetailItem
icon={Leaf}
label="Points Weed/Hash"
value={`${client.point || 0} points`}
/>
<DetailItem
icon={Wind}
label="Points Zipette"
value={`${client.points_zipette || 0} points`}
/>
<DetailItem
icon={AlertCircle}
label="Amendes"
value={`${client.amende}€`}
/>
<DetailItem
icon={XCircle}
label="Annulations"
value={String(client.cancellations_count)}
/>
{client.last_penalty_reason && (
<DetailItem
icon={AlertCircle}
label="Dernière pénalité"
value={client.last_penalty_reason}
fullWidth
/>
)}
</DetailSection>
<DetailSection title="Informations système">
<DetailItem
icon={Shield}
label="ID Client"
value={`#${client.id}`}
/>
</DetailSection>
<div className="modal-actions">
<button className="btn-cancel" onClick={onClose}>
<XCircle size={18} />
Fermer
</button>
</div>
</div>
</div>
</div>
);
}
interface DetailSectionProps {
title: string;
children: React.ReactNode;
}
function DetailSection({ title, children }: DetailSectionProps) {
return (
<div className="client-detail-section">
<h4>{title}</h4>
<div className="detail-grid">{children}</div>
</div>
);
}
interface DetailItemProps {
icon: React.ComponentType<{ size: number }>;
label: string;
value: string;
fullWidth?: boolean;
}
function DetailItem({ icon: Icon, label, value, fullWidth }: DetailItemProps) {
return (
<div className={`detail-item ${fullWidth ? "full" : ""}`}>
<label>
<Icon size={16} />
{label}
</label>
<p>{value}</p>
</div>
);
}
interface EmptyStateProps {
icon: React.ComponentType<{ size: number }>;
title: string;
message: string;
}
function EmptyState({ icon: Icon, title, message }: EmptyStateProps) {
return (
<div className="empty-state">
<div style={{ opacity: 0.3 }}>
<Icon size={64} />
</div>
<h3>{title}</h3>
<p>{message}</p>
</div>
);
}
export default UsersManagement;