feat: add template checkpoint edit dialog and vendor management components
- Implemented TemplateCheckpointEditDialog for creating and editing inspection checkpoints. - Added VendorActions component for managing vendor actions including edit, activate/deactivate, and delete. - Created VendorContext for managing vendor state across components. - Developed VendorGeneralInfo component to display detailed vendor information. - Introduced AedSymbol and Money components for consistent currency representation. - Added PromptDialog for user input prompts throughout the application. - Implemented RelationLink component for unified related-data display in CRUD tables. - Created InspectionTemplatesClient for API interactions related to inspection templates.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import type { ColumnDef, Row } from "@tanstack/react-table"
|
||||
import type { ComponentType } from "react"
|
||||
import { MoreHorizontal, Pencil, Trash2 } from "lucide-react"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
@@ -10,9 +11,20 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/components/ui/dropdown-menu"
|
||||
|
||||
export type ActionsColumnExtraItem<TData> = {
|
||||
label: string
|
||||
icon?: ComponentType<{ className?: string }>
|
||||
onClick: (row: TData) => void | Promise<unknown>
|
||||
variant?: "default" | "destructive"
|
||||
/** Hide the item entirely when false. Useful for permission gates. */
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type ActionsColumnOptions<TData> = {
|
||||
onEdit?: (row: TData) => void
|
||||
onDelete?: (row: TData) => Promise<unknown>
|
||||
/** Extra menu items rendered between Edit and Delete. */
|
||||
extraItems?: (row: TData) => ActionsColumnExtraItem<TData>[]
|
||||
}
|
||||
|
||||
export function createActionsColumn<TData extends { id: string | number }>(
|
||||
@@ -53,6 +65,24 @@ function ActionsCell<TData extends { id: string | number }>({
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{options.extraItems?.(row.original)
|
||||
.filter((item) => !item.hidden)
|
||||
.map((item) => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={item.label}
|
||||
variant={item.variant}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
void item.onClick(row.original)
|
||||
}}
|
||||
>
|
||||
{Icon ? <Icon className="size-3.5 text-muted-foreground" /> : null}
|
||||
{item.label}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
{options.onDelete && (
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useQueryStates } from "nuqs"
|
||||
import { useQuery, useQueryClient, type UseQueryOptions } from "@tanstack/react-query"
|
||||
import type { DataViewChangeEvent, DataViewPaginationState, DataViewSorting } from "./types"
|
||||
import { dataTableSearchParams } from "./search-params"
|
||||
import { type CrudListResponse } from "@garage/api"
|
||||
import { DEFAULT_PER_PAGE, type CrudListResponse } from "@garage/api"
|
||||
|
||||
type DataTableClient = {
|
||||
list(query?: any): Promise<any>
|
||||
}
|
||||
|
||||
type DataTableParams = {
|
||||
page: number
|
||||
per_page: number
|
||||
sort_by: string | null
|
||||
sort_order: "asc" | "desc" | null
|
||||
}
|
||||
|
||||
type UseDataTableQueryOptions<C extends DataTableClient> = {
|
||||
queryKey: string[]
|
||||
client: C
|
||||
queryOptions?: Omit<UseQueryOptions<CrudListResponse<C>>, "queryKey" | "queryFn">
|
||||
extraParams?: Record<string, unknown>
|
||||
/** When true, pagination/sorting state lives in component state instead of URL params. Used for tables inside dialogs so they don't share state with the parent page. */
|
||||
localState?: boolean
|
||||
}
|
||||
|
||||
export function useDataTableQuery<C extends DataTableClient>({
|
||||
@@ -22,8 +32,25 @@ export function useDataTableQuery<C extends DataTableClient>({
|
||||
client,
|
||||
queryOptions,
|
||||
extraParams,
|
||||
localState = false,
|
||||
}: UseDataTableQueryOptions<C>) {
|
||||
const [params, setParams] = useQueryStates(dataTableSearchParams)
|
||||
const [urlParams, setUrlParams] = useQueryStates(dataTableSearchParams)
|
||||
const [localParams, setLocalParams] = useState<DataTableParams>({
|
||||
page: 1,
|
||||
per_page: DEFAULT_PER_PAGE,
|
||||
sort_by: null,
|
||||
sort_order: null,
|
||||
})
|
||||
|
||||
const params: DataTableParams = localState ? localParams : urlParams
|
||||
const setParams = (next: Partial<DataTableParams>) => {
|
||||
if (localState) {
|
||||
setLocalParams((prev) => ({ ...prev, ...next }))
|
||||
} else {
|
||||
setUrlParams(next)
|
||||
}
|
||||
}
|
||||
|
||||
const _queryKey = [...queryKey, params, ...(extraParams ? [extraParams] : [])]
|
||||
const query = useQuery<CrudListResponse<C>>({
|
||||
queryKey: _queryKey,
|
||||
@@ -74,7 +101,10 @@ export function useDataTableQuery<C extends DataTableClient>({
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const invalidateQuery = () => {
|
||||
queryClient.invalidateQueries({ queryKey: _queryKey })
|
||||
// Prefix-match on the base route key so all paginated/sorted variants of
|
||||
// the list refetch — exact-matching the params object is flaky because
|
||||
// nuqs returns a fresh reference per render.
|
||||
queryClient.invalidateQueries({ queryKey, refetchType: "active" })
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user