Files
Project-Management-V2/frontend/app/main/configuration/company-data/components/grade-table/index.tsx
Your NamebaishaliHolocron b9ac5ae0b2 first commit
2026-06-15 12:57:03 +05:30

147 lines
5.5 KiB
TypeScript

"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { Search, Award } from "lucide-react";
import {
ColumnDef,
DataTableLayout,
Card,
Input,
} from "ikoncomponents";
import { getAllGrade } from "@/app/utils/api/companyData/gradeApi.ts";
import { GradeResponseDto } from "@/app/utils/api/companyData/gradeApi.ts/type";
function GradeDataTable() {
const [gradeTableData, setGradeTableData] = useState<GradeResponseDto[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState("");
// DataTableLayout defaults to list view and has no prop to start in grid, so
// once mounted we click its built-in "Grid View" toggle once to default to cards.
const tableContainerRef = useRef<HTMLDivElement>(null);
const defaultedToGrid = useRef(false);
const filteredGradeTableData = useMemo(() => {
if (!search.trim()) return gradeTableData;
return gradeTableData.filter((gradeData) =>
gradeData.grade?.toLowerCase().includes(search.toLowerCase()),
);
}, [gradeTableData, search]);
const fetchGradeTableData = async () => {
setIsLoading(true);
try {
const gradeData = await getAllGrade();
setGradeTableData(gradeData?.content || []);
} catch (error) {
console.error("Error fetching grade data:", error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchGradeTableData();
}, []);
// Switch DataTableLayout into grid (card) view as soon as it mounts, once.
useEffect(() => {
if (isLoading || defaultedToGrid.current) return;
const gridButton =
tableContainerRef.current?.querySelector<HTMLButtonElement>(
'button[title="Grid View"]',
);
if (gridButton) {
gridButton.click();
defaultedToGrid.current = true;
}
}, [isLoading]);
// ikoncomponents passes the row object directly to cell(), not { row }.
const columns: ColumnDef<GradeResponseDto>[] = [
{
accessorKey: "grade",
header: "Grade",
cell: (row) => <span>{row.grade || "n/a"}</span>,
},
];
return (
<div className="space-y-5 p-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-lg font-semibold">Grades</h1>
<p className="mt-1 text-xs text-muted-foreground">
{filteredGradeTableData.length} of {gradeTableData.length} grades
</p>
</div>
</div>
<div ref={tableContainerRef}>
<DataTableLayout
data={filteredGradeTableData}
columns={columns}
extraTools={{
keyExtractor: (row: GradeResponseDto) => row.id,
totalPages: 1,
currentPage: 1,
isLoading,
onReload: fetchGradeTableData,
actionNode: (
<div className="flex w-full min-w-[260px] flex-1 items-center gap-2 rounded-lg border px-3 h-9">
<Search className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="h-4 w-px shrink-0 bg-border" />
<Input
placeholder="Search grades..."
className="h-8 border-none p-0 text-sm shadow-none placeholder:text-muted-foreground focus-visible:ring-0"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
),
gridComponent: (data: GradeResponseDto[]) => (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
{data.length > 0 ? (
data.map((gradeItem) => (
<Card
key={gradeItem.id}
// flex-row + py-4 explicitly override the base Card's
// `flex flex-col gap-6 py-6`, otherwise the icon stacks on
// top and the content centers with a lot of empty space.
className="group relative flex flex-row items-center gap-3.5 overflow-hidden rounded-xl border py-4! px-4 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-md"
>
{/* Left accent rail */}
<span className="absolute inset-y-0 left-0 w-1 bg-primary/60 opacity-0 transition-opacity duration-200 group-hover:opacity-100" />
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary transition-transform duration-200 group-hover:scale-105">
<Award className="h-5 w-5" />
</div>
<div className="min-w-0">
<p className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
Grade
</p>
<p
className="truncate text-sm font-semibold leading-tight text-foreground"
title={gradeItem.grade}
>
{gradeItem.grade || "-"}
</p>
</div>
</Card>
))
) : (
<div className="col-span-2 flex items-center justify-center rounded-lg border border-dashed p-8 text-muted-foreground sm:col-span-3 lg:col-span-4">
No grades found.
</div>
)}
</div>
),
}}
/>
</div>
</div>
);
}
export default GradeDataTable;