init
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user