espace-paie-odentas/components/TicketTimeline.tsx
2025-10-15 00:40:57 +02:00

186 lines
No EOL
7.1 KiB
TypeScript

import React from "react";
import { Check, Clock, User } from "lucide-react";
type TimelineStatus = "open" | "waiting_staff" | "waiting_client" | "closed";
interface TimelineProps {
currentStatus: TimelineStatus;
lastMessageBy?: "client" | "staff";
}
const TIMELINE_STEPS = [
{ id: "open", label: "Ticket ouvert", icon: Check },
{ id: "waiting_staff", label: "Attente retour Odentas", icon: Clock },
{ id: "waiting_client", label: "Attente retour client", icon: User },
{ id: "closed", label: "Résolu", icon: Check },
] as const;
function getStepStatus(stepId: string, currentStatus: TimelineStatus, lastMessageBy?: "client" | "staff") {
if (stepId === "open") {
return "completed"; // Toujours complété car le ticket existe
}
if (stepId === "waiting_staff") {
// Complété si on a dépassé cette étape
if (currentStatus === "waiting_client" || currentStatus === "closed") return "completed";
// En cours si on attend le staff
if (currentStatus === "waiting_staff") return "current";
// En cours si ticket ouvert et dernier message du client
if (currentStatus === "open" && lastMessageBy === "client") return "current";
return "upcoming";
}
if (stepId === "waiting_client") {
// Complété si on a atteint la fermeture
if (currentStatus === "closed") return "completed";
// En cours si on attend le client
if (currentStatus === "waiting_client") return "current";
// En cours si ticket ouvert et dernier message du staff
if (currentStatus === "open" && lastMessageBy === "staff") return "current";
return "upcoming";
}
if (stepId === "closed") {
return currentStatus === "closed" ? "current" : "upcoming";
}
return "upcoming";
}
export default function TicketTimeline({ currentStatus, lastMessageBy }: TimelineProps) {
return (
<div className="bg-white rounded-xl border border-slate-200 p-4 sm:p-6 mb-6">
{/* Version mobile : vertical */}
<div className="flex flex-col space-y-4 sm:hidden">
{TIMELINE_STEPS.map((step, index) => {
const status = getStepStatus(step.id, currentStatus, lastMessageBy);
const Icon = step.icon;
return (
<React.Fragment key={step.id}>
<div className="flex items-center gap-3">
{/* Icône */}
<div
className={`
relative flex items-center justify-center w-10 h-10 rounded-full border-2 transition-all duration-300 flex-shrink-0
${status === "current"
? "bg-blue-600 border-blue-600 text-white shadow-lg shadow-blue-500/25"
: status === "completed"
? "bg-emerald-600 border-emerald-600 text-white"
: "bg-slate-100 border-slate-300 text-slate-400"
}
`}
>
<Icon className="w-4 h-4" />
{status === "current" && (
<div className="absolute -inset-1 bg-blue-600/20 rounded-full animate-pulse" />
)}
</div>
{/* Label */}
<div className="flex-1">
<div
className={`
text-sm font-medium transition-colors duration-300
${status === "current"
? "text-blue-600"
: status === "completed"
? "text-emerald-600"
: "text-slate-500"
}
`}
>
{step.label}
</div>
</div>
</div>
{/* Connecteur vertical */}
{index < TIMELINE_STEPS.length - 1 && (
<div className="flex justify-start pl-5">
<div
className={`
w-0.5 h-4 transition-all duration-500
${status === "completed"
? "bg-gradient-to-b from-emerald-500 to-blue-500"
: status === "current" && getStepStatus(TIMELINE_STEPS[index + 1].id, currentStatus, lastMessageBy) !== "upcoming"
? "bg-gradient-to-b from-blue-500 to-emerald-500"
: "bg-slate-200"
}
`}
/>
</div>
)}
</React.Fragment>
);
})}
</div>
{/* Version desktop : horizontal */}
<div className="hidden sm:flex items-center justify-between">
{TIMELINE_STEPS.map((step, index) => {
const status = getStepStatus(step.id, currentStatus, lastMessageBy);
const Icon = step.icon;
return (
<React.Fragment key={step.id}>
<div className="flex flex-col items-center space-y-2">
{/* Icône */}
<div
className={`
relative flex items-center justify-center w-12 h-12 rounded-full border-2 transition-all duration-300
${status === "current"
? "bg-blue-600 border-blue-600 text-white shadow-lg shadow-blue-500/25"
: status === "completed"
? "bg-emerald-600 border-emerald-600 text-white"
: "bg-slate-100 border-slate-300 text-slate-400"
}
`}
>
<Icon className="w-5 h-5" />
{status === "current" && (
<div className="absolute -inset-1 bg-blue-600/20 rounded-full animate-pulse" />
)}
</div>
{/* Label */}
<div className="text-center">
<div
className={`
text-sm font-medium transition-colors duration-300
${status === "current"
? "text-blue-600"
: status === "completed"
? "text-emerald-600"
: "text-slate-500"
}
`}
>
{step.label}
</div>
</div>
</div>
{/* Connecteur horizontal */}
{index < TIMELINE_STEPS.length - 1 && (
<div className="flex-1 px-4">
<div
className={`
h-0.5 transition-all duration-500
${status === "completed"
? "bg-gradient-to-r from-emerald-500 to-blue-500"
: status === "current" && getStepStatus(TIMELINE_STEPS[index + 1].id, currentStatus, lastMessageBy) !== "upcoming"
? "bg-gradient-to-r from-blue-500 to-emerald-500"
: "bg-slate-200"
}
`}
/>
</div>
)}
</React.Fragment>
);
})}
</div>
</div>
);
}