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,146 @@
"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;

View File

@@ -0,0 +1,150 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { Search, Briefcase } from "lucide-react";
import {
ColumnDef,
DataTableLayout,
Card,
Input,
} from "ikoncomponents";
import { getAllRoles } from "@/app/utils/api/companyData/roleApi.ts";
interface RoleData {
id: string;
role: string;
}
function RoleDataTable() {
const [roleTableData, setRoleTableData] = useState<RoleData[]>([]);
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 filteredRoleTableData = useMemo(() => {
if (!search.trim()) return roleTableData;
return roleTableData.filter((roleData) =>
roleData.role?.toLowerCase().includes(search.toLowerCase()),
);
}, [roleTableData, search]);
const fetchRoleTableData = async () => {
setIsLoading(true);
try {
const roleData = await getAllRoles();
setRoleTableData(roleData || []);
} catch (error) {
console.error("Error fetching role data:", error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchRoleTableData();
}, []);
// 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<RoleData>[] = [
{
accessorKey: "role",
header: "Role",
cell: (row) => <span>{row.role || "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">Roles</h1>
<p className="mt-1 text-xs text-muted-foreground">
{filteredRoleTableData.length} of {roleTableData.length} roles
</p>
</div>
</div>
<div ref={tableContainerRef}>
<DataTableLayout
data={filteredRoleTableData}
columns={columns}
extraTools={{
keyExtractor: (row: RoleData) => row.id,
totalPages: 1,
currentPage: 1,
isLoading,
onReload: fetchRoleTableData,
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 roles..."
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: RoleData[]) => (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
{data.length > 0 ? (
data.map((roleItem) => (
<Card
key={roleItem.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">
<Briefcase className="h-5 w-5" />
</div>
<div className="min-w-0">
<p className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
Role
</p>
<p
className="truncate text-sm font-semibold leading-tight text-foreground"
title={roleItem.role}
>
{roleItem.role || "-"}
</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 roles found.
</div>
)}
</div>
),
}}
/>
</div>
</div>
);
}
export default RoleDataTable;

View File

@@ -0,0 +1,28 @@
"use client";
import { CustomTabs, TabArray } from "ikoncomponents";
import GradeDataTable from "../grade-table";
import RoleDataTable from "../role-table";
const tabArray: TabArray[] = [
{
tabName: "Role",
tabId: "tab-role",
default: true,
tabContent: <RoleDataTable />,
},
{
tabName: "Grade",
tabId: "tab-grade",
default: false,
tabContent: <GradeDataTable />,
},
];
export default function CompanyDataTab() {
return (
<div className="p-4">
<CustomTabs tabArray={tabArray} />
</div>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import { RenderAppBreadcrumb } from "ikoncomponents";
export default function CompanyDataLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>){
return (
<>
<RenderAppBreadcrumb breadcrumb={{ level: 2, title: "Company Data", href: "/configuration/company-data" }} />
{children}
</>
);
}

View File

@@ -0,0 +1,9 @@
import CompanyDataTab from "./components/tabs";
export default async function CompanyData() {
return (
<div className="py-4 space-y-4 w-full h-full">
<CompanyDataTab />
</div>
);
}