Mohammad Khyata e1ef6fa2ea fix many bugs
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 19:03:31 +03:00

197 lines
5.5 KiB
TypeScript

"use client"
import { useState } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { toast } from "sonner"
import { confirm } from "@/shared/components/confirm-dialog"
import type { DataViewChangeEvent, DataViewPaginationState, DataViewSorting } from "@/shared/data-view/table-view"
// ── Types ──
export type CrudDialogClient = {
list(query?: any): Promise<any>
create(payload: any): Promise<any>
update(id: string, payload: any): Promise<any>
destroy(id: string): Promise<any>
}
export type UseCrudDialogOptions<TClient extends CrudDialogClient> = {
queryKey: string[]
getClient: () => TClient
resourceLabel?: string
}
type CrudListShape = {
data?: unknown
meta?: {
last_page?: number
total?: number
}
pagination?: {
last_page?: number
total?: number
}
}
function normalizeCrudListResponse(response: unknown): {
items: any[]
meta?: { last_page?: number; total?: number }
} {
const root = (response ?? {}) as CrudListShape
const directData = root.data
if (Array.isArray(directData)) {
return { items: directData, meta: root.meta ?? root.pagination }
}
if (directData && typeof directData === "object") {
const nested = directData as CrudListShape
if (Array.isArray(nested.data)) {
return {
items: nested.data,
meta: nested.meta ?? nested.pagination ?? root.meta ?? root.pagination,
}
}
}
return { items: [], meta: root.meta ?? root.pagination }
}
// ── Hook ──
export function useCrudDialog<TClient extends CrudDialogClient>({
queryKey,
getClient,
resourceLabel = "item",
}: UseCrudDialogOptions<TClient>) {
const client = getClient()
const queryClient = useQueryClient()
// ── Local pagination state (no URL pollution) ──
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [sortBy, setSortBy] = useState<string | null>(null)
const [sortOrder, setSortOrder] = useState<"asc" | "desc" | null>(null)
// ── Form dialog state ──
const [isFormOpen, setIsFormOpen] = useState(false)
const [editingId, setEditingId] = useState<string | null>(null)
const [editingItem, setEditingItem] = useState<any>(null)
const fullQueryKey = [...queryKey, { page, pageSize, sortBy, sortOrder }]
const { data, isLoading } = useQuery({
queryKey: fullQueryKey,
queryFn: async () => {
const params: Record<string, unknown> = { page, per_page: pageSize }
if (sortBy) params.sort_by = sortBy
if (sortOrder) params.sort_order = sortOrder
try {
return await client.list(params)
} catch {
// Some endpoints ignore/reject pagination params; retry without params.
return client.list()
}
},
})
const normalized = normalizeCrudListResponse(data)
const items = normalized.items
const meta = normalized.meta
const pagination: DataViewPaginationState = {
page,
pageSize,
pageCount: meta?.last_page ?? 1,
total: meta?.total ?? 0,
}
const sorting: DataViewSorting = sortBy
? [{ id: sortBy, desc: sortOrder === "desc" }]
: []
const handleChange = (event: DataViewChangeEvent) => {
switch (event.type) {
case "pagination":
setPage(event.pagination.page)
setPageSize(event.pagination.pageSize)
break
case "sorting": {
const sort = event.sorting[0]
setSortBy(sort?.id ?? null)
setSortOrder(sort ? (sort.desc ? "desc" : "asc") : null)
setPage(1)
break
}
}
}
const invalidateQuery = () => {
queryClient.invalidateQueries({ queryKey })
}
const { mutateAsync: deleteItem } = useMutation({
mutationFn: (id: string) => {
const promise = client.destroy(id)
toast.promise(promise, {
loading: `Deleting ${resourceLabel}...`,
success: `${resourceLabel} deleted`,
error: `Failed to delete ${resourceLabel}`,
})
return promise
},
onSuccess: invalidateQuery,
})
const openCreate = () => {
setEditingId(null)
setEditingItem(null)
setIsFormOpen(true)
}
const openEdit = (row: any) => {
setEditingId(String(row.id))
setEditingItem(row)
setIsFormOpen(true)
}
const closeForm = () => {
setIsFormOpen(false)
setEditingId(null)
setEditingItem(null)
}
const handleDelete = async (row: any) => {
const confirmed = await confirm({
title: `Delete this ${resourceLabel}?`,
description: "This action cannot be undone.",
confirmLabel: "Delete",
variant: "destructive",
})
if (confirmed) await deleteItem(String(row.id))
}
const handleFormSuccess = () => {
invalidateQuery()
closeForm()
}
return {
items,
isLoading,
pagination,
sorting,
handleChange,
isFormOpen,
editingId,
editingItem,
openCreate,
openEdit,
closeForm,
handleDelete,
handleFormSuccess,
invalidateQuery,
}
}