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:
2026-05-18 12:08:42 +04:00
parent 54d11f01b4
commit 4bfd8c84a9
111 changed files with 5002 additions and 1047 deletions
@@ -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 {