feat: integrate dialog close context in vendor select field and CRUD dialog components feat: enhance vendor general info to format status using utility function feat: implement form dialog context for managing dialog close actions feat: add async select field dialog close context for better form handling fix: update form mutation hook to close dialog on successful submission feat: extend document print types to include expense and credit note feat: add settings update payload type to include logo and other fields feat: create employee attendance and work history pages with resource management feat: implement payment made and received detail pages with actions feat: add quick shortcuts component for easy navigation in the dashboard feat: create actions for payment made and received with print and delete options feat: implement dialog close context for better dialog management feat: add error parsing utility for improved error handling in API responses
171 lines
6.3 KiB
TypeScript
171 lines
6.3 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { useQueryClient } from "@tanstack/react-query"
|
|
import {
|
|
CheckCircle2,
|
|
ClipboardList,
|
|
Eye,
|
|
MoreHorizontal,
|
|
Pencil,
|
|
PlayCircle,
|
|
Printer,
|
|
Share2,
|
|
Trash2,
|
|
XCircle,
|
|
} from "lucide-react"
|
|
import { toast } from "sonner"
|
|
|
|
import { Button } from "@/shared/components/ui/button"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/shared/components/ui/dropdown-menu"
|
|
import { useAuthApi } from "@/shared/useApi"
|
|
import { confirm } from "@/shared/components/confirm-dialog"
|
|
import { InspectionShareDialog } from "@/modules/inspections/inspection-share-dialog"
|
|
import { INSPECTION_ROUTES } from "@garage/api"
|
|
import { useDocumentPrint } from "@/shared/hooks/use-document-print"
|
|
|
|
type InspectionStatus = "in_progress" | "completed" | "cancelled"
|
|
|
|
type InspectionRow = {
|
|
id: number | string
|
|
status?: InspectionStatus | string
|
|
}
|
|
|
|
export function InspectionRowActions({
|
|
inspection,
|
|
onEdit,
|
|
onDelete,
|
|
}: {
|
|
inspection: InspectionRow
|
|
onEdit: () => void
|
|
onDelete: () => Promise<unknown>
|
|
}) {
|
|
const router = useRouter()
|
|
const api = useAuthApi()
|
|
const queryClient = useQueryClient()
|
|
const { print, isPrinting } = useDocumentPrint()
|
|
const [shareOpen, setShareOpen] = useState(false)
|
|
|
|
const inspectionId = String(inspection.id)
|
|
const status = inspection.status as InspectionStatus | undefined
|
|
|
|
const invalidate = () => {
|
|
queryClient.invalidateQueries({ queryKey: [INSPECTION_ROUTES.INDEX] })
|
|
queryClient.invalidateQueries({ queryKey: [INSPECTION_ROUTES.BY_ID, inspectionId] })
|
|
}
|
|
|
|
const changeStatus = async (next: InspectionStatus, label: string) => {
|
|
try {
|
|
await api.inspections.changeStatus({
|
|
id: Number(inspectionId),
|
|
status: next,
|
|
} as any)
|
|
toast.success(label)
|
|
invalidate()
|
|
} catch (e: any) {
|
|
// Surface backend validation / permission errors verbatim so the user
|
|
// (and we) can see what actually failed.
|
|
const errors = e?.payload?.errors
|
|
const firstFieldError = errors && typeof errors === "object"
|
|
? (Object.values(errors)[0] as string[])?.[0]
|
|
: null
|
|
const msg =
|
|
firstFieldError ??
|
|
e?.payload?.message ??
|
|
e?.message ??
|
|
`Failed: ${label}`
|
|
toast.error(msg)
|
|
// eslint-disable-next-line no-console
|
|
console.error("changeStatus failed", { inspectionId, next, error: e })
|
|
}
|
|
}
|
|
|
|
const handleDelete = async (e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
const confirmed = await confirm({
|
|
title: "Delete this inspection?",
|
|
description: "This will remove the inspection and all its checkpoints. This cannot be undone.",
|
|
confirmLabel: "Delete",
|
|
variant: "destructive",
|
|
})
|
|
if (confirmed) {
|
|
try {
|
|
await onDelete()
|
|
toast.success("Inspection deleted")
|
|
} catch (err: any) {
|
|
toast.error(err?.payload?.message ?? err?.message ?? "Failed to delete inspection")
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<MoreHorizontal className="size-4" />
|
|
<span className="sr-only">Inspection actions</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" onClick={(e) => e.stopPropagation()}>
|
|
<DropdownMenuItem onClick={() => router.push(`/sales/inspections/${inspectionId}`)}>
|
|
<Eye className="size-3.5 text-muted-foreground" /> View detail
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => router.push(`/sales/inspections/${inspectionId}/checkpoints`)}>
|
|
<ClipboardList className="size-3.5 text-muted-foreground" /> Open checkpoints
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={onEdit}>
|
|
<Pencil className="size-3.5 text-muted-foreground" /> Edit
|
|
</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem onClick={() => print("inspection", inspectionId, "print")} disabled={isPrinting}>
|
|
<Printer className="size-3.5 text-muted-foreground" /> {isPrinting ? "Printing..." : "Print"}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setShareOpen(true)}>
|
|
<Share2 className="size-3.5 text-muted-foreground" /> Share with customer
|
|
</DropdownMenuItem>
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
{status === "cancelled" || status === "completed" ? (
|
|
<DropdownMenuItem onClick={() => changeStatus("in_progress", "Reopened — back to in progress")}>
|
|
<PlayCircle className="size-3.5 text-amber-600" /> Reopen as in progress
|
|
</DropdownMenuItem>
|
|
) : (
|
|
<DropdownMenuItem onClick={() => changeStatus("completed", "Inspection marked completed")}>
|
|
<CheckCircle2 className="size-3.5 text-emerald-600" /> Mark completed
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
{status !== "cancelled" && (
|
|
<DropdownMenuItem onClick={() => changeStatus("cancelled", "Inspection cancelled")}>
|
|
<XCircle className="size-3.5 text-rose-600" /> Cancel
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
|
<Trash2 className="size-3.5" /> Delete
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
|
|
<InspectionShareDialog
|
|
open={shareOpen}
|
|
onOpenChange={setShareOpen}
|
|
inspectionId={inspectionId}
|
|
/>
|
|
</DropdownMenu>
|
|
)
|
|
}
|