parts and areas tested
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user