first commit

This commit is contained in:
Your NamebaishaliHolocron
2026-06-15 12:57:03 +05:30
commit b9ac5ae0b2
398 changed files with 49583 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
"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>
);
}