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,390 @@
"use client";
import { ColumnDef } from "ikoncomponents";
import { useEffect, useRef, useState, useMemo } from "react";
import {
Button,
ComboboxInput,
ComboboxItemProps,
DataTableLayout,
Input,
} from "ikoncomponents";
import { Plus } from "lucide-react";
import { createProjectApi, getProjectsApi } from "@/app/utils/api/projectApi";
import ProjectCard from "./projectCard/projectCard";
import { Project } from "./types/project";
import { mapProjectToCard } from "./types/project-mapper";
import CreateProjectModal from "./projectModal/projectModal";
import { ProjectFormData } from "./projectModal/zodProject";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useAppCache } from "@/app/utils/context/AppCacheContext";
import {
createUserMaps,
findUsersInRole,
} from "@/app/utils/api/user-dashboard-platform/mappingFunction";
// import FileUploader from "./uploadFile";
export default function ProjectsPage() {
const [projects, setProjects] = useState<Project[]>([]);
const [statusesDropdown, setStatusesDropdown] = useState<ComboboxItemProps[]>(
[],
);
const [managerDropdown, setManagerDropdown] = useState<ComboboxItemProps[]>(
[],
);
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
const [filterQuery, setFilterQuery] = useState<
Partial<Record<keyof Project, string>>
>({});
// let statusesDropdown: ComboboxItemProps[] = [];
const router = useRouter();
// Project Manager options + the userId -> userName map come from the shared
// app-wide cache. The map resolves a project's manager UUID to a display name.
const { projectManagerOptions, userNameMap } = useAppCache();
// DataTableLayout has no prop to set the initial view, so once the table is
// rendered we click its built-in "Grid View" toggle once to default to cards.
const tableContainerRef = useRef<HTMLDivElement>(null);
const defaultedToGrid = useRef(false);
const setDropdownFilters = async (statusDropdown: ComboboxItemProps[]) => {
setStatusesDropdown(statusDropdown);
};
// =====================
// DataTable Columns
// =====================
const columns: ColumnDef<Project>[] = [
{
accessorKey: "projectName",
header: () => <div className="text-center">Project Name</div>,
cell: (row) => {
console.log("row", row.projectIdentifier);
return (
<Link
className="hover:underline"
href={`/main/planning/projects/${row.projectIdentifier}`}
>
{row.projectName}
</Link>
);
},
},
{
accessorKey: "projectIdentifier",
header: () => <div className="text-center">Code</div>,
// cell: ({ row }) => <span>{row.original.projectIdentifier}</span>,
},
{
accessorKey: "contractedStartDate",
header: () => <div className="text-center">Start Date</div>,
// cell: ({ row }) => <span>{row.original.contractedStartDate}</span>,
},
{
accessorKey: "contractedEndDate",
header: () => <div className="text-center">End Date</div>,
// cell: ({ row }) => <span>{row.original.contractedEndDate}</span>,
},
{
accessorKey: "projectStatus",
header: () => <div className="text-center">Status</div>,
// cell: ({ row }) => <span>{row.original.projectStatus}</span>,
},
];
// =====================
// Load projects
// =====================
useEffect(() => {
const loadProjects = async () => {
try {
const data = await getProjectsApi();
if (Array.isArray(data)) {
setProjects(data);
} else {
console.error("Failed to fetch projects, received:", data);
setProjects([]);
}
} finally {
setLoading(false);
}
};
const loadManagerData = async () => {
const projectManagerMemberIds = await findUsersInRole("Project manager");
const { nameMap } = await createUserMaps();
const dropdown: ComboboxItemProps[] = [
{ label: "All Managers", value: "All Managers" },
];
projectManagerMemberIds?.map((id) => {
const userName = nameMap.get(`${id}`);
dropdown.push({ label: userName, value: id });
});
setManagerDropdown(dropdown);
};
loadManagerData();
loadProjects();
}, []);
// Switch the table into grid (card) view as soon as it mounts, once.
useEffect(() => {
if (loading || defaultedToGrid.current) return;
const gridButton =
tableContainerRef.current?.querySelector<HTMLButtonElement>(
'button[title="Grid View"]',
);
if (gridButton) {
gridButton.click();
defaultedToGrid.current = true;
}
}, [loading]);
// Build the manager filter dropdown from the cached Project Manager options.
useEffect(() => {
const dropdown: ComboboxItemProps[] = [
{ label: "All Managers", value: "All Managers" },
];
projectManagerOptions.forEach((opt) => {
dropdown.push({ label: opt.label, value: opt.value });
});
setManagerDropdown(dropdown);
}, [projectManagerOptions]);
useEffect(() => {
const statuses: string[] = ["Active", "Ongoing", "Completed"];
const statusDropdown: ComboboxItemProps[] = [
{ label: "All Statuses", value: "All Statuses" },
];
// projects.map((project) => {
// if (!statuses.includes(project.projectStatus)) {
// statuses.push(project.projectStatus);
// }
// });
statuses.map((status) =>
statusDropdown.push({ label: status, value: status }),
);
setDropdownFilters(statusDropdown);
}, [projects]);
// =====================
// Create project
// =====================
const handleCreateProject = async (data: ProjectFormData) => {
try {
console.log("Project data------", data);
const response = await createProjectApi(data);
console.log("Project payload-----", response);
setOpen(false);
const refreshed = await getProjectsApi();
if (Array.isArray(refreshed)) {
setProjects(refreshed);
}
} catch (error) {
console.error("Failed to create project", error);
}
};
// =====================
// Search filter
// =====================
const filteredProjects = useMemo(() => {
if (!search.trim() && Object.keys(filterQuery).length === 0)
return projects;
return projects.filter((project: Project) => {
const searchMatch = project.projectName
?.toLowerCase()
.includes(search.toLowerCase());
const filterMatch = Object.entries(filterQuery).every(([key, value]) => {
// Check if the project matches the filter query
return value ? project[key] === value : true;
});
return searchMatch && filterMatch;
});
}, [projects, search, filterQuery]);
const onFilterChange = (key: string, value: string) => {
setFilterQuery((prev) => {
// "All …" entries are show-everything sentinels, not real filter values —
// map them to "" so they don't get matched against real fields (e.g. a UUID
// projectManager). Covers "All Statuses", "All Managers", etc.
const isSentinel = value.startsWith("All ");
return {
...prev,
[key]: isSentinel || value === "All Managers" ? "" : value,
};
});
};
return (
<div className="p-6 space-y-6">
{/* ================= HEADER ================= */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-semibold">Projects</h1>
<p className="text-muted-foreground mt-1">
{filteredProjects.length} of {projects.length} projects
</p>
</div>
<Button onClick={() => setOpen(true)} className="flex gap-2">
<Plus size={18} />
New Project
</Button>
</div>
{/* ================= CONTENT ================= */}
{loading ? (
<div className="text-sm text-muted-foreground">Loading projects...</div>
) : (
<>
{/* LIST VIEW */}
<div ref={tableContainerRef}>
<DataTableLayout
data={filteredProjects}
columns={columns}
extraTools={{
keyExtractor: (row: Project) => {
console.log("row", row);
return row.projectIdentifier;
},
onRowClick: (row) => {
router.push(
`/main/planning/projects/${row.projectIdentifier}`,
);
},
totalPages: 0,
currentPage: 0,
actionNode: (
<div className="flex flex-wrap items-center gap-3">
{/* Search */}
<div className="flex-1 min-w-[260px]">
<Input
placeholder="Search projects..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
{/* Status Filter */}
{/* <Button variant="outline" className="flex gap-2">
All Statuses <ChevronDown size={16} />
</Button> */}
<div className="grid grid-cols-2 gap-2">
<ComboboxInput
items={statusesDropdown}
placeholder="Select Status"
defaultValue="All Statuses"
onSelect={(value) => {
if (Array.isArray(value)) {
onFilterChange("projectStatus", value[0]);
} else {
onFilterChange("projectStatus", value);
}
}}
/>
{/* Manager Filter */}
{/* <Button variant="outline" className="">
All Managers <ChevronDown size={16} />
</Button> */}
<ComboboxInput
items={managerDropdown}
placeholder="Select Manager"
defaultValue="All Managers"
onSelect={(value) => {
if (Array.isArray(value)) {
onFilterChange("projectManager", value[0]);
} else {
onFilterChange("projectManager", value);
}
}}
/>
</div>
{/* Source Filter */}
{/* <Button variant="outline" className="">
All Sources <ChevronDown size={16} />
</Button> */}
{/* View Toggle */}
{/* <div className="flex border rounded-lg overflow-hidden">
<button
onClick={() => setView("grid")}
className={`p-2 ${view === "grid" ? "bg-muted" : ""}`}
>
<Grid size={18} />
</button>
<button
onClick={() => setView("list")}
className={`p-2 ${view === "list" ? "bg-muted" : ""}`}
>
<List size={18} />
</button>
</div> */}
</div>
),
gridComponent: (data: Project[]) => (
<div className="grid gap-3 grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{data &&
data.length > 0 &&
data.map((project, index) => {
const cardProps = mapProjectToCard(project);
const resolved = userNameMap.get(
project.projectManager,
);
// TEMP diagnostic: shows whether the manager UUID resolves to a name.
console.log("[manager check]", {
project: project.projectName,
managerId: project.projectManager,
mapHasId: userNameMap.has(project.projectManager),
resolvedName: resolved,
mapSize: userNameMap.size,
});
const managerName =
resolved ?? cardProps.projectManager;
return (
<ProjectCard
key={index}
{...cardProps}
projectManager={managerName}
/>
);
})}
</div>
),
}}
/>
</div>
</>
)}
{/* ================= MODAL ================= */}
<CreateProjectModal
open={open}
onClose={setOpen}
onSubmit={handleCreateProject}
projectManagerDropdownOptions={managerDropdown}
/>
</div>
);
}