2026-04-07 06:32:40 +03:00

150 lines
5.4 KiB
TypeScript

"use client"
import React, { useState } from "react"
import type { ColumnDef } from "@tanstack/react-table"
import { Settings2, Plus, ArrowLeft } from "lucide-react"
import { Button } from "@/shared/components/ui/button"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/shared/components/ui/dialog"
import { ScrollArea } from "@/shared/components/ui/scroll-area"
import { DataTable } from "@/shared/data-view/table-view"
import { createActionsColumn } from "@/shared/data-view/table-view"
import { useCrudDialog, type CrudDialogClient, type UseCrudDialogOptions } from "./use-crud-dialog"
// ── Types ──
export type CrudDialogProps<TClient extends CrudDialogClient> = UseCrudDialogOptions<TClient> & {
/** Dialog title shown in the header */
title: string
/** Table columns (receives `openEdit` and `handleDelete` helpers) */
columns: (helpers: {
openEdit: (row: any) => void
handleDelete: (row: any) => Promise<void>
}) => ColumnDef<any>[]
/** Render the create/edit form */
renderForm: (props: {
resourceId: string | null
initialData: any
onSuccess: () => void
}) => React.ReactNode
/** Optional trigger button; defaults to a settings icon button */
trigger?: React.ReactNode
/** Custom trigger class */
triggerClassName?: string
}
// ── Component ──
export function CrudDialog<TClient extends CrudDialogClient>({
title,
columns: columnsFn,
renderForm,
trigger,
triggerClassName,
...hookOptions
}: CrudDialogProps<TClient>) {
const [isOpen, setIsOpen] = useState(false)
const crud = useCrudDialog(hookOptions)
const columns = columnsFn({
openEdit: crud.openEdit,
handleDelete: crud.handleDelete,
})
// Add actions column
const allColumns: ColumnDef<any>[] = [
...columns,
createActionsColumn({
onEdit: crud.openEdit,
onDelete: crud.handleDelete,
}),
]
const handleClose = () => {
setIsOpen(false)
crud.closeForm()
}
return (
<>
{trigger ? (
<div onClick={() => setIsOpen(true)}>{trigger}</div>
) : (
<Button
type="button"
size="icon"
variant="ghost"
className={triggerClassName ?? "h-5 w-5"}
onClick={() => setIsOpen(true)}
title={`Manage ${title}`}
>
<Settings2 className="h-3.5 w-3.5" />
</Button>
)}
<Dialog open={isOpen} onOpenChange={(v) => { if (!v) handleClose() }}>
<DialogContent className="min-w-2xl max-w-3xl">
<DialogHeader>
<div className="flex items-center gap-2">
{crud.isFormOpen && (
<Button
type="button"
size="icon"
variant="ghost"
className="h-7 w-7"
onClick={crud.closeForm}
>
<ArrowLeft className="h-4 w-4" />
</Button>
)}
<DialogTitle className="text-xl font-bold">
{crud.isFormOpen
? (crud.editingId ? `Edit ${title}` : `New ${title}`)
: title}
</DialogTitle>
</div>
</DialogHeader>
<ScrollArea className="max-h-[75vh]">
{crud.isFormOpen ? (
<div className="px-1">
{renderForm({
resourceId: crud.editingId,
initialData: crud.editingItem,
onSuccess: crud.handleFormSuccess,
})}
</div>
) : (
<div className="space-y-3 px-1">
<div className="flex justify-end">
<Button
type="button"
size="sm"
onClick={crud.openCreate}
>
<Plus className="h-4 w-4" />
Add {title}
</Button>
</div>
<DataTable
columns={allColumns}
data={crud.items}
pagination={crud.pagination}
sorting={crud.sorting}
onChange={crud.handleChange}
isLoading={crud.isLoading}
/>
</div>
)}
</ScrollArea>
</DialogContent>
</Dialog>
</>
)
}