Files
Your NamebaishaliHolocron b9ac5ae0b2 first commit
2026-06-15 12:57:03 +05:30

194 lines
6.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { calculateDurationInDays } from "@/app/utils/function/projectDuration";
import {
Card,
CardHeader,
CardTitle,
CardContent,
CardDescription,
} from "ikoncomponents";
import { Badge } from "ikoncomponents";
import { Button } from "ikoncomponents";
import { CalendarDays, Clock, Building2, UserRound, ArrowRight, Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
export type ProjectCardProps = {
projectIdentifier: string;
projectName: string;
projectDescription: string;
projectStatus: string;
projectClient:string;
productType: string;
contractedStartDate: string ;
contractedEndDate: string ;
projectManager:string;
};
// A small palette of accent themes. Only the icons, left border and the solid
// button are colored — the panels stay neutral so text is always readable.
// Each entry carries explicit light + dark classes and uses 600-weight fills
// with white text so contrast holds in both themes. Classes are static strings
// (not interpolated) so Tailwind keeps them in the build.
const ACCENTS = [
{
border: "border-l-blue-500",
icon: "text-blue-600 dark:text-blue-400",
button:
"bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500",
},
{
border: "border-l-emerald-500",
icon: "text-emerald-600 dark:text-emerald-400",
button:
"bg-emerald-600 text-white hover:bg-emerald-700 dark:bg-emerald-600 dark:hover:bg-emerald-500",
},
{
border: "border-l-violet-500",
icon: "text-violet-600 dark:text-violet-400",
button:
"bg-violet-600 text-white hover:bg-violet-700 dark:bg-violet-600 dark:hover:bg-violet-500",
},
{
border: "border-l-amber-500",
icon: "text-amber-600 dark:text-amber-400",
button:
"bg-amber-600 text-white hover:bg-amber-700 dark:bg-amber-600 dark:hover:bg-amber-500",
},
{
border: "border-l-rose-500",
icon: "text-rose-600 dark:text-rose-400",
button:
"bg-rose-600 text-white hover:bg-rose-700 dark:bg-rose-600 dark:hover:bg-rose-500",
},
{
border: "border-l-cyan-500",
icon: "text-cyan-600 dark:text-cyan-400",
button:
"bg-cyan-600 text-white hover:bg-cyan-700 dark:bg-cyan-600 dark:hover:bg-cyan-500",
},
] as const;
export default function ProjectCard({
projectIdentifier,
projectName,
projectDescription,
projectStatus,
projectClient,
productType,
projectManager,
contractedStartDate,
contractedEndDate,
}: ProjectCardProps) {
const router = useRouter();
// Shows a spinner on the button from the moment it's clicked until this card
// unmounts (i.e. the project details route has taken over). The details page
// then renders its own spinner while it finishes loading.
const [isNavigating, setIsNavigating] = useState(false);
const duration = calculateDurationInDays(contractedStartDate,contractedEndDate);
const handleViewDetails = () => {
setIsNavigating(true);
router.push(`/main/planning/projects/${projectIdentifier}`);
};
// Pick a stable accent per project so the same card is always the same color.
const accent = (() => {
const key = projectIdentifier || projectName || "";
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = (hash * 31 + key.charCodeAt(i)) >>> 0;
}
return ACCENTS[hash % ACCENTS.length];
})();
// Map a project status to a badge style so the state reads at a glance.
const statusVariant = (() => {
switch (projectStatus?.toLowerCase()) {
case "completed":
return "verified" as const;
case "active":
case "ongoing":
return "default" as const;
default:
return "secondary" as const;
}
})();
return (
<Card
className={`group flex flex-col rounded-2xl border border-l-4 ${accent.border} shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-lg`}
>
<CardHeader className="flex flex-row items-start justify-between gap-3 pb-3">
<div className="min-w-0">
<CardTitle className="truncate text-lg font-semibold leading-tight">
{projectName}
</CardTitle>
</div>
<Badge variant={statusVariant} className="shrink-0 text-xs px-3 py-1">
{projectStatus || "Unknown"}
</Badge>
</CardHeader>
<CardContent className="flex flex-1 flex-col space-y-4">
{/* Description */}
<CardDescription className="line-clamp-2 min-h-10 text-sm">
{projectDescription || "No description provided."}
</CardDescription>
{/* Meta — neutral panel, colored icons, full-width rows so values stay readable */}
<div className="space-y-2.5 rounded-xl border bg-muted/40 p-3 text-sm">
<div className="flex items-center justify-between gap-3">
<div className="flex min-w-0 items-center gap-2 text-muted-foreground">
<CalendarDays className={`h-4 w-4 shrink-0 ${accent.icon}`} />
<span className="truncate text-foreground">
{contractedStartDate || "to"} {contractedEndDate || "—"}
</span>
</div>
<div className="flex shrink-0 items-center gap-1.5 text-muted-foreground">
<Clock className={`h-4 w-4 shrink-0 ${accent.icon}`} />
<span className="text-foreground">{duration}d</span>
</div>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<Building2 className={`h-4 w-4 shrink-0 ${accent.icon}`} />
<span className="truncate text-foreground">{projectClient || "—"}</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<UserRound className={`h-4 w-4 shrink-0 ${accent.icon}`} />
<span className="truncate text-foreground">{projectManager || "—"}</span>
</div>
</div>
<Button
onClick={handleViewDetails}
disabled={isNavigating}
className={`mt-auto flex w-full items-center justify-center gap-2 border-0 ${accent.button}`}
>
{isNavigating ? (
<>
Loading
<Loader2 className="h-4 w-4 animate-spin" />
</>
) : (
<>
View Details
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
</>
)}
</Button>
</CardContent>
</Card>
);
}