parts and areas tested

This commit is contained in:
2025-11-24 21:04:14 +03:00
parent a5a7a7e7b5
commit eedfed1a65
131 changed files with 5429 additions and 471 deletions

View File

@@ -6,7 +6,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel,
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useSortable } from "@dnd-kit/sortable"
import { IconGripVertical } from "@tabler/icons-react"
import { IconGripVertical, IconHandClick } from "@tabler/icons-react"
import { useIsMobile } from "@/hooks/use-mobile"
import { Separator } from "@/components/ui/separator"
import { ColumnDef, flexRender, Row } from "@tanstack/react-table"
@@ -14,119 +14,7 @@ import { TableCell, TableRow } from "@/components/ui/table"
import { CSS } from "@dnd-kit/utilities"
import { schema, schemaType } from "./schema"
import { dateToLocaleString } from "@/lib/utils"
import { Pencil, Trash } from "lucide-react"
// function TableCellViewer({ item }: { item: schemaType }) {
// const isMobile = useIsMobile();
// return (
// <Drawer direction={isMobile ? "bottom" : "right"}>
// <DrawerTrigger asChild>
// <Button variant="link" className="text-foreground w-fit px-0 text-left">
// {item.email ?? "Unknown Email"}
// </Button>
// </DrawerTrigger>
// <DrawerContent>
// <DrawerHeader className="gap-1">
// <h2 className="text-lg font-semibold">{item.email}</h2>
// <p className="text-sm text-muted-foreground">
// User details
// </p>
// </DrawerHeader>
// <div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
// {/* BASIC INFO */}
// <div className="grid grid-cols-2 gap-4">
// <div>
// <Label>Email</Label>
// <Input value={item.email ?? ""} readOnly />
// </div>
// <div>
// <Label>Phone</Label>
// <Input value={item.phone ?? ""} readOnly />
// </div>
// <div>
// <Label>Tag</Label>
// <Input value={item.tag ?? ""} readOnly />
// </div>
// <div>
// <Label>Active</Label>
// <Input value={item.active ? "Active" : "Inactive"} readOnly />
// </div>
// </div>
// <Separator />
// {/* DATES */}
// <div className="grid grid-cols-2 gap-4">
// <div>
// <Label>Created At</Label>
// <Input
// value={item.createdAt ? new Date(item.createdAt).toLocaleString() : ""}
// readOnly
// />
// </div>
// <div>
// <Label>Updated At</Label>
// <Input
// value={item.updatedAt ? new Date(item.updatedAt).toLocaleString() : ""}
// readOnly
// />
// </div>
// </div>
// <Separator />
// {/* TOKENS */}
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <Button variant="outline" className="w-full">
// Collection Tokens ({item.collectionTokens?.tokens?.length ?? 0})
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent className="w-72">
// <DropdownMenuLabel>Tokens</DropdownMenuLabel>
// <DropdownMenuSeparator />
// {(item.collectionTokens?.tokens ?? []).length === 0 && (<DropdownMenuItem disabled>No tokens found</DropdownMenuItem>)}
// {(item.collectionTokens?.tokens ?? []).map((t, i) => (
// <DropdownMenuItem key={i} className="flex flex-col gap-2">
// <div className="grid grid-cols-2 gap-2 w-full">
// <Input value={t.prefix} readOnly />
// <Input value={t.token} readOnly />
// </div>
// </DropdownMenuItem>
// ))}
// </DropdownMenuContent>
// </DropdownMenu>
// </div>
// <DrawerFooter>
// <DrawerClose asChild>
// <Button variant="outline">Close</Button>
// </DrawerClose>
// </DrawerFooter>
// </DrawerContent>
// </Drawer>
// );
// }
function DragHandle({ id }: { id: number }) {
const { attributes, listeners } = useSortable({ id })
return (
<Button {...attributes} {...listeners} variant="ghost" size="icon" className="text-muted-foreground size-7 hover:bg-transparent">
<IconGripVertical className="text-muted-foreground size-3" />
<span className="sr-only">Drag to reorder</span>
</Button>
)
}
import { Pencil, Trash, TextSelect } from "lucide-react"
export function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original._id })
@@ -142,102 +30,112 @@ export function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
)
}
function getColumns(router: any, deleteHandler: (id: string) => void): ColumnDef<schemaType>[] {
function getColumns(router: any, activeRoute: string, deleteHandler: (id: string) => void): ColumnDef<schemaType>[] {
return [
{
accessorKey: "uuid",
header: "UUID",
cell: ({ getValue }) => (<div className="font-mono text-xs bg-muted px-2 py-1 rounded break-all">{String(getValue())}</div>),
accessorKey: "buildType.token",
header: "Token",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "firstName",
header: "First Name",
accessorKey: "collectionToken",
header: "Collection Token",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "surname",
header: "Surname",
accessorKey: "info.govAddressCode",
header: "Gov Address Code",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "middleName",
header: "Middle Name",
accessorKey: "info.buildName",
header: "Build Name",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "sexCode",
header: "Sex",
accessorKey: "info.buildNo",
header: "Build No",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "personRef",
header: "Person Ref",
accessorKey: "info.maxFloor",
header: "Max Floor",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "personTag",
header: "Person Tag",
accessorKey: "info.undergroundFloor",
header: "Underground Floor",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "fatherName",
header: "Father Name",
accessorKey: "info.buildDate",
header: "Build Date",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "motherName",
header: "Mother Name",
accessorKey: "info.decisionPeriodDate",
header: "Decision Period Date",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "countryCode",
header: "Country",
accessorKey: "info.taxNo",
header: "Tax No",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "nationalIdentityId",
header: "National ID",
accessorKey: "info.liftCount",
header: "Lift Count",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "birthPlace",
header: "Birth Place",
accessorKey: "info.heatingSystem",
header: "Heating System",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "active",
header: "Active",
cell: ({ getValue }) => getValue() ? (<div className="text-green-600 font-medium">Yes</div>) : (<div className="text-red-600 font-medium">No</div>),
accessorKey: "info.coolingSystem",
header: "Cooling System",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "isConfirmed",
header: "Confirmed",
cell: ({ getValue }) => getValue() ? (<div className="text-green-600 font-medium">Yes</div>) : (<div className="text-red-600 font-medium">No</div>),
accessorKey: "info.hotWaterSystem",
header: "Hot Water System",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "birthDate",
header: "Birth Date",
cell: ({ getValue }) => dateToLocaleString(getValue() as string),
accessorKey: "info.blockServiceManCount",
header: "Block Service Man Count",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "createdAt",
header: "Created",
cell: ({ getValue }) => dateToLocaleString(getValue() as string),
accessorKey: "info.securityServiceManCount",
header: "Security Service Man Count",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "updatedAt",
header: "Updated",
cell: ({ getValue }) => dateToLocaleString(getValue() as string),
accessorKey: "info.garageCount",
header: "Garage Count",
cell: ({ getValue }) => getValue(),
},
{
accessorKey: "expiryStarts",
header: "Expiry Starts",
cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-",
},
{
accessorKey: "expiryEnds",
header: "Expiry Ends",
cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-",
accessorKey: "info.managementRoomId",
header: "Management Room ID",
cell: ({ getValue }) => getValue(),
},
{
id: "actions",
header: "Actions",
cell: ({ row }) => {
return (
<div>
<Button className="bg-amber-400 text-black border-amber-400" variant="outline" size="sm" onClick={() => { router.push(`/people/update?uuid=${row.original.uuid}`) }}>
<div className="flex flex-row gap-2">
<Button className="bg-blue-600 border-blue-600 text-white" variant="outline" size="sm" onClick={() => { router.push(`/${activeRoute}/add?build=${row.original._id}`) }}>
<IconHandClick />
</Button>
<Button className="bg-amber-400 text-black border-amber-400" variant="outline" size="sm" onClick={() => { router.push(`/builds/update?uuid=${row.original._id}`) }}>
<Pencil />
</Button>
<Button className="bg-red-700 text-white border-red-700 mx-4" variant="outline" size="sm" onClick={() => { deleteHandler(row.original.uuid || "") }}>
<Button className="bg-red-700 text-white border-red-700" variant="outline" size="sm" onClick={() => { deleteHandler(row.original._id || "") }}>
<Trash />
</Button>
</div>

View File

@@ -17,6 +17,10 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
import {
IconBorderLeftPlus,
IconBuildingBank,
IconBuildingBridge,
IconBuildingChurch,
IconChevronDown,
IconChevronLeft,
IconChevronRight,
@@ -70,7 +74,7 @@ import {
import { schemaType } from "./schema"
import { getColumns, DraggableRow } from "./columns"
import { useRouter } from "next/navigation"
import { useDeleteUserMutation } from "@/pages/users/queries"
import { useDeleteBuildMutation } from "@/pages/builds/queries"
export function BuildDataTable({
data,
@@ -87,10 +91,29 @@ export function BuildDataTable({
pageSize?: number,
onPageChange: (page: number) => void,
onPageSizeChange: (size: number) => void,
refetchTable: () => void,
refetchTable: () => void
}) {
const router = useRouter();
const routeSelections = [
{
url: 'build-parts',
name: 'Build Parts',
icon: <IconBuildingBank />
},
{
url: 'build-areas',
name: 'Build Areas',
icon: <IconBuildingChurch />
},
{
url: 'build-sites',
name: 'Build Sites',
icon: <IconBuildingBridge />
},
]
const [activeRoute, setActiveRoute] = React.useState(routeSelections[0].url);
const [rowSelection, setRowSelection] = React.useState({})
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
@@ -99,9 +122,9 @@ export function BuildDataTable({
const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}))
const dataIds = React.useMemo<UniqueIdentifier[]>(() => data?.map(({ _id }) => _id) || [], [data])
const deleteMutation = useDeleteUserMutation()
const deleteMutation = useDeleteBuildMutation()
const deleteHandler = (id: string) => { deleteMutation.mutate({ uuid: id }); setTimeout(() => { refetchTable() }, 400) }
const columns = getColumns(router, deleteHandler);
const columns = getColumns(router, activeRoute, deleteHandler);
const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize])
const totalPages = Math.ceil(totalCount / pageSize)
@@ -128,14 +151,9 @@ export function BuildDataTable({
const handlePageSizeChange = (value: string) => { const newSize = Number(value); onPageSizeChange(newSize); onPageChange(1) }
return (
<Tabs
defaultValue="outline"
className="w-full flex-col justify-start gap-6"
>
<Tabs defaultValue="outline" className="w-full flex-col justify-start gap-6">
<div className="flex items-center justify-between px-4 lg:px-6">
<Label htmlFor="view-selector" className="sr-only">
View
</Label>
<Label htmlFor="view-selector" className="sr-only">View</Label>
<Select defaultValue="outline">
<SelectTrigger className="flex w-fit @4xl/main:hidden" size="sm" id="view-selector">
<SelectValue placeholder="Select a view" />
@@ -157,7 +175,6 @@ export function BuildDataTable({
<IconChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
{table.getAllColumns().filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide()).map((column) => {
return (
@@ -168,15 +185,34 @@ export function BuildDataTable({
})}
</DropdownMenuContent>
</DropdownMenu>
<Button variant="outline" size="sm" onClick={() => { router.push("/people/add") }}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<IconBorderLeftPlus />
<span className="hidden lg:inline">Selected To Add</span>
<span className="lg:hidden">Add</span>
<IconChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
{routeSelections.map((column) => {
return (
<DropdownMenuCheckboxItem key={column.url} className="capitalize" checked={activeRoute === column.url} onCheckedChange={(value) => setActiveRoute(column.url)} >
<div className="flex items-center gap-2">
{column.icon}{column.name}
</div>
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
<Button variant="outline" size="sm" onClick={() => { router.push("/builds/add") }}>
<IconPlus />
<span className="hidden lg:inline">Add Person</span>
<span className="hidden lg:inline">Add Build</span>
</Button>
</div>
</div>
<TabsContent value="outline"
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
>
<TabsContent value="outline" className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6">
<div className="overflow-hidden rounded-lg border">
<DndContext collisionDetection={closestCenter} modifiers={[restrictToVerticalAxis]} sensors={sensors} id={sortableId} >
<Table>

View File

@@ -2,26 +2,30 @@ import { z } from "zod";
export const schema = z.object({
_id: z.string(),
uuid: z.string(),
firstName: z.string().nullable().optional(),
surname: z.string().nullable().optional(),
middleName: z.string().nullable().optional(),
sexCode: z.string().nullable().optional(),
personRef: z.string().nullable().optional(),
personTag: z.string().nullable().optional(),
fatherName: z.string().nullable().optional(),
motherName: z.string().nullable().optional(),
countryCode: z.string().nullable().optional(),
nationalIdentityId: z.string().nullable().optional(),
birthPlace: z.string().nullable().optional(),
birthDate: z.string().nullable().optional(),
taxNo: z.string().nullable().optional(),
birthname: z.string().nullable().optional(),
expiryStarts: z.string().nullable().optional(),
expiryEnds: z.string().nullable().optional(),
createdAt: z.string().nullable().optional(),
updatedAt: z.string().nullable().optional(),
buildType: z.object({
token: z.string(),
typeToken: z.string(),
type: z.string(),
}),
collectionToken: z.string(),
info: z.object({
govAddressCode: z.string(),
buildName: z.string(),
buildNo: z.string(),
maxFloor: z.number(),
undergroundFloor: z.number(),
buildDate: z.string(),
decisionPeriodDate: z.string(),
taxNo: z.string(),
liftCount: z.number(),
heatingSystem: z.boolean(),
coolingSystem: z.boolean(),
hotWaterSystem: z.boolean(),
blockServiceManCount: z.number(),
securityServiceManCount: z.number(),
garageCount: z.number(),
managementRoomId: z.number(),
})
});
export type schemaType = z.infer<typeof schema>;
export type schemaType = z.infer<typeof schema>;