finances/src/components/entityPageClientComponents.tsx
2024-03-17 01:22:58 +01:00

208 lines
8.2 KiB
TypeScript

'use client';
import { Entity } from '@prisma/client';
import React, { useState } from 'react';
import { CellContext } from '@tanstack/table-core';
import { Button } from '@/components/ui/button';
import { Edit, Trash } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import EntityForm from '@/components/form/entityForm';
import { DataTable } from '@/components/ui/data-table';
import { columns } from '@/app/entities/columns';
import { z } from 'zod';
import { entityFormSchema } from '@/lib/form-schemas/entityFormSchema';
import { ActionResponse } from '@/lib/types/actionResponse';
import { Input } from '@/components/ui/input';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
} from '@/components/ui/alert-dialog';
import { useMediaQuery } from '@/lib/hooks/useMediaQuery';
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer';
export default function EntityPageClientContent({entities, onSubmit, onDelete, className}: {
entities: Entity[],
onSubmit: (data: z.infer<typeof entityFormSchema>) => Promise<ActionResponse>,
onDelete: (id: number) => Promise<ActionResponse>,
className: string,
}) {
const isDesktop = useMediaQuery('(min-width: 768px)');
const router = useRouter();
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [selectedEntity, setSelectedEntity] = useState<Entity | undefined>(undefined);
async function handleSubmit(data: z.infer<typeof entityFormSchema>) {
const response = await onSubmit(data);
router.refresh();
setIsEditDialogOpen(false);
return response;
}
async function handleDelete(id: number | undefined) {
if (!id) {
return;
}
const response = await onDelete(id);
toast(sonnerContent(response));
if (response.redirect) {
router.push(response.redirect);
}
router.refresh();
setIsDeleteDialogOpen(false);
return response;
}
function filterEntities(entities: Entity[], filter: string) {
const filterChars = filter.toLowerCase().split('');
const filterCharCounts: Record<string, number> = {};
// Count character occurrences in the filter
filterChars.forEach(char => {
filterCharCounts[char] = (filterCharCounts[char] || 0) + 1;
});
return entities.filter(entity => {
const entityChars = entity.name.toLowerCase().split('');
const entityCharCounts: Record<string, number> = {};
// Check if entity has enough of each character
for (const char of entityChars) {
entityCharCounts[char] = (entityCharCounts[char] || 0) + 1;
}
// Ensure all filter characters were found
return Object.keys(filterCharCounts).every(char => {
return entityCharCounts[char] >= filterCharCounts[char];
});
});
}
const actionCell = ({row}: CellContext<Entity, unknown>) => {
const entity = row.original as Entity;
return (
<div className="flex items-center space-x-4">
<Button
variant="ghost"
className="h-8 w-8 p-0"
onClick={() => {
setSelectedEntity(entity);
setIsEditDialogOpen(true);
}}>
<span className="sr-only">Edit entity</span>
<Edit className="h-4 w-4"/>
</Button>
<Button
variant="ghost"
className="h-8 w-8 p-0"
onClick={() => {
setSelectedEntity(entity);
setIsDeleteDialogOpen(true);
}}
>
<span className="sr-only">Delete entity</span>
<Trash className="h-4 w-4"/>
</Button>
</div>
);
};
const [filter, setFilter] = useState<string>('');
return (
<div className={className}>
<div className="flex items-center justify-between w-full">
<p className="text-3xl font-semibold">Entities</p>
{/* Edit dialog */}
{
isDesktop ? (
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<DialogTrigger asChild>
<Button
onClick={() => {
setSelectedEntity(undefined);
setIsEditDialogOpen(true);
}}>
Create Entity
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{selectedEntity?.id ? 'Update Entity' : 'Create Entity'}</DialogTitle>
</DialogHeader>
<EntityForm
value={selectedEntity}
onSubmit={handleSubmit}
className="grid grid-cols-1 md:grid-cols-2 gap-4 py-4"/>
</DialogContent>
</Dialog>
) : (
<Drawer open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<DrawerTrigger asChild>
<Button
onClick={() => {
setSelectedEntity(undefined);
setIsEditDialogOpen(true);
}}>
Create Entity
</Button>
</DrawerTrigger>
<DrawerContent className="p-4">
<DrawerHeader>
<DrawerTitle>{selectedEntity?.id ? 'Update Entity' : 'Create Entity'}</DrawerTitle>
</DrawerHeader>
<EntityForm
value={selectedEntity}
onSubmit={handleSubmit}
className="grid grid-cols-1 md:grid-cols-2 gap-4 py-4"/>
</DrawerContent>
</Drawer>
)
}
</div>
{/* Filter input */}
<Input
value={filter}
onChange={(event) => setFilter(event.target.value)}
placeholder="Filter entities"/>
{/* Data Table */}
<DataTable
className="w-full"
columns={columns(actionCell)}
data={filterEntities(entities, filter)}
pagination/>
{/* Delete confirmation dialog */}
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>Delete Entity?</AlertDialogHeader>
<p>Are your sure you want to delete the entity {selectedEntity?.name}?</p>
<AlertDialogFooter>
<AlertDialogCancel>
Cancel
</AlertDialogCancel>
<AlertDialogAction onClick={() => handleDelete(selectedEntity?.id)}>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}