This commit is contained in:
2026-03-27 16:20:46 +03:00
commit 13b56d4960
260 changed files with 111131 additions and 0 deletions
@@ -0,0 +1,14 @@
export { useResourcePage } from "./use-resource-page"
export { ResourcePage } from "./resource-page"
export type {
ResourcePageClient,
ResourceItem,
UseResourcePageOptions,
} from "./use-resource-page"
export type {
ResourceFormProps,
ResourcePageColumnHelpers,
ResourcePageProps,
} from "./resource-page"
@@ -0,0 +1,89 @@
"use client"
import React from "react"
import { DashboardHeader } from "@/base/components/layout/dashboard"
import DashboardPage from "@/base/components/layout/dashboard/dashboard-page"
import FormDialog from "@/shared/components/form-dialog"
import { Card, CardContent } from "@/shared/components/ui/card"
import { DataTable, type ActionsColumnOptions } from "@/shared/data-view/table-view"
import { useResourcePage, type UseResourcePageOptions, type ResourceItem, type ResourcePageClient } from "./use-resource-page"
import type { ColumnDef } from "@tanstack/react-table"
export type ResourceFormProps<TClient extends ResourcePageClient> = {
resourceId: string | null
initialData: ResourceItem<TClient> | null
onSuccess: () => void
}
export type ResourcePageColumnHelpers<TClient extends ResourcePageClient> = {
actionsColumn: (options?: Partial<ActionsColumnOptions<ResourceItem<TClient>>>) => ColumnDef<ResourceItem<TClient>, unknown>
openEdit: (row: ResourceItem<TClient>) => void
deleteItem: (id: string) => Promise<unknown>
}
export type ResourcePageProps<TClient extends ResourcePageClient> = UseResourcePageOptions<TClient> & {
title: string
columns: ColumnDef<ResourceItem<TClient>>[] | ((helpers: ResourcePageColumnHelpers<TClient>) => ColumnDef<ResourceItem<TClient>>[])
renderForm: (props: ResourceFormProps<TClient>) => React.ReactNode
pageTitle?: string
paramKey?: string
}
export function ResourcePage<TClient extends ResourcePageClient>({
title,
columns: columnsProp,
renderForm,
pageTitle,
routeKey,
getClient,
queryOptions,
paramKey,
}: ResourcePageProps<TClient>) {
type TItem = ResourceItem<TClient>
const page = useResourcePage<TClient>({ routeKey, getClient, queryOptions, paramKey })
const columns = typeof columnsProp === "function"
? columnsProp({
actionsColumn: page.actionsColumn,
openEdit: page.openEdit,
deleteItem: page.deleteItem,
})
: columnsProp
type ListResponse = { data?: TItem[] }
const responseData = page.data as ListResponse | undefined
return (
<DashboardPage
header={
<DashboardHeader
actions={
<FormDialog title={title} paramKey={paramKey}>
{(resourceId) =>
renderForm({
resourceId,
initialData: page.selectedItem,
onSuccess: () => page.invalidateQuery(),
})
}
</FormDialog>
}
/>
}
title={pageTitle}
>
<Card>
<CardContent>
<DataTable
columns={columns}
data={(responseData?.data ?? []) as TItem[]}
pagination={page.pagination}
sorting={page.sorting}
onChange={page.handleChange}
isLoading={page.isLoading}
/>
</CardContent>
</Card>
</DashboardPage>
)
}
@@ -0,0 +1,102 @@
"use client"
import { useState } from "react"
import { useMutation } from "@tanstack/react-query"
import { toast } from "sonner"
import { confirm } from "@/shared/components/confirm-dialog"
import { useAuthApi } from "@/shared/useApi"
import { useFormDialog } from "@/shared/components/form-dialog"
import { useDataTableQuery, createActionsColumn, type ActionsColumnOptions } from "@/shared/data-view/table-view"
import type { ColumnDef } from "@tanstack/react-table"
import type { CrudListItem, BaseCrudItem, CrudListResponse } from "@garage/api"
import type { UseQueryOptions } from "@tanstack/react-query"
type ApiInstance = ReturnType<typeof useAuthApi>
export type ResourcePageClient = {
list(query?: any): Promise<any>
destroy(id: string): Promise<any>
}
export type ResourceItem<TClient> = CrudListItem<TClient> & BaseCrudItem
export type UseResourcePageOptions<TClient extends ResourcePageClient> = {
routeKey: string
getClient: (api: ApiInstance) => TClient
queryOptions?: Omit<UseQueryOptions<CrudListResponse<TClient>>, "queryKey" | "queryFn">
paramKey?: string
}
export function useResourcePage<TClient extends ResourcePageClient>({
routeKey,
getClient,
queryOptions,
paramKey,
}: UseResourcePageOptions<TClient>) {
type TItem = ResourceItem<TClient>
const api = useAuthApi()
const client = getClient(api)
const { open: openDialog, close: closeDialog, isOpen, resourceId } = useFormDialog(paramKey)
const [selectedItem, setSelectedItem] = useState<TItem | null>(null)
const tableQuery = useDataTableQuery({
queryKey: [routeKey],
client,
queryOptions,
})
const { mutateAsync: deleteItem } = useMutation({
mutationFn: (id: string) => {
const promise = client.destroy(id)
toast.promise(promise, {
loading: "Deleting...",
success: "Deleted successfully",
error: "Failed to delete",
})
return promise
},
onSuccess: () => tableQuery.invalidateQuery(),
})
const openEdit = (row: TItem) => {
setSelectedItem(row)
openDialog(String(row.id))
}
const openCreate = () => {
setSelectedItem(null)
openDialog()
}
const actionsColumn = (options?: Partial<ActionsColumnOptions<TItem>>): ColumnDef<TItem, unknown> =>
createActionsColumn<TItem>({
onEdit: (row) => openEdit(row),
onDelete: async (row) => {
const confirmed = await confirm({
title: "Delete this item?",
description: "This action cannot be undone.",
confirmLabel: "Delete",
variant: "destructive",
})
if (confirmed) await deleteItem(String(row.id))
},
...options,
})
return {
api,
client,
isDialogOpen: isOpen,
dialogResourceId: resourceId,
selectedItem,
setSelectedItem,
openEdit,
openCreate,
openDialog,
closeDialog,
...tableQuery,
deleteItem,
actionsColumn,
}
}