All checks were successful
Build Frontend / build (push) Successful in 1m12s
Refactored dashboard pages for improved layout and visual consistency using Card components, motion animations, and updated color schemes. Added an OrderTimeline component to the order details page to visualize order lifecycle. Improved customer management page with better sorting, searching, and a detailed customer dialog. Updated storefront settings page with a modernized layout and clearer sectioning.
87 lines
3.8 KiB
TypeScript
87 lines
3.8 KiB
TypeScript
"use client"
|
|
|
|
import { CheckCircle2, Circle, Clock, Package, Truck, Flag } from "lucide-react"
|
|
import { motion } from "framer-motion"
|
|
|
|
interface OrderTimelineProps {
|
|
status: string;
|
|
orderDate: Date | string;
|
|
paidAt?: Date | string;
|
|
completedAt?: Date | string;
|
|
}
|
|
|
|
const steps = [
|
|
{ status: "unpaid", label: "Ordered", icon: Clock },
|
|
{ status: "paid", label: "Paid", icon: CheckCircle2 },
|
|
{ status: "acknowledged", label: "Processing", icon: Package },
|
|
{ status: "shipped", label: "Shipped", icon: Truck },
|
|
{ status: "completed", label: "Completed", icon: Flag },
|
|
]
|
|
|
|
export default function OrderTimeline({ status, orderDate, paidAt }: OrderTimelineProps) {
|
|
const currentStatusIndex = steps.findIndex(step =>
|
|
step.status === status ||
|
|
(status === "confirming" && step.status === "unpaid") ||
|
|
(status === "acknowledged" && step.status === "paid") // Processed is after paid
|
|
);
|
|
|
|
// If status is "confirming", it's basically "unpaid" for the timeline
|
|
// If status is "acknowledged", it's "Processing"
|
|
|
|
const getStepStatus = (index: number) => {
|
|
// Basic logic to determine if a step is completed, current, or pending
|
|
let effectiveIndex = currentStatusIndex;
|
|
if (status === "confirming") effectiveIndex = 0;
|
|
if (status === "paid") effectiveIndex = 1;
|
|
if (status === "acknowledged") effectiveIndex = 2;
|
|
if (status === "shipped") effectiveIndex = 3;
|
|
if (status === "completed") effectiveIndex = 4;
|
|
|
|
if (index < effectiveIndex) return "completed";
|
|
if (index === effectiveIndex) return "current";
|
|
return "pending";
|
|
};
|
|
|
|
return (
|
|
<div className="relative flex justify-between items-center w-full px-4 py-8">
|
|
{/* Connector Line */}
|
|
<div className="absolute left-10 right-10 top-1/2 h-0.5 bg-muted -translate-y-1/2 z-0">
|
|
<motion.div
|
|
className="h-full bg-primary"
|
|
initial={{ width: "0%" }}
|
|
animate={{ width: `${(Math.max(0, steps.findIndex(s => s.status === status)) / (steps.length - 1)) * 100}%` }}
|
|
transition={{ duration: 1, ease: "easeInOut" }}
|
|
/>
|
|
</div>
|
|
|
|
{steps.map((step, index) => {
|
|
const stepStatus = getStepStatus(index);
|
|
const Icon = step.icon;
|
|
|
|
return (
|
|
<div key={step.label} className="relative flex flex-col items-center z-10">
|
|
<motion.div
|
|
initial={{ scale: 0.8, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ delay: index * 0.1 }}
|
|
className={`flex items-center justify-center w-10 h-10 rounded-full border-2 transition-colors duration-500 ${stepStatus === "completed"
|
|
? "bg-primary border-primary text-primary-foreground"
|
|
: stepStatus === "current"
|
|
? "bg-background border-primary text-primary ring-4 ring-primary/10"
|
|
: "bg-background border-muted text-muted-foreground"
|
|
}`}
|
|
>
|
|
<Icon className="h-5 w-5" />
|
|
</motion.div>
|
|
<div className="absolute top-12 whitespace-nowrap text-xs font-medium tracking-tight">
|
|
<p className={stepStatus === "pending" ? "text-muted-foreground" : "text-foreground"}>
|
|
{step.label}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)
|
|
}
|