feat: enhance invoice and job card forms with new fields, validation, and print functionality
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select"
|
||||
import { Ellipsis, Pencil, Trash2, ShieldCheck, Check, X } from "lucide-react"
|
||||
import { Ellipsis, Pencil, Trash2, ShieldCheck, Check, X, Printer } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import { toast } from "sonner"
|
||||
@@ -33,6 +33,7 @@ import { ESTIMATE_ROUTES } from "@garage/api"
|
||||
import { DatePickerField, TimePickerField } from "@/shared/components/form"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { EmployeeCombobox, type EmployeeOption } from "../employees/employee-combobox"
|
||||
import { useDocumentPrint } from "@/shared/hooks/use-document-print"
|
||||
|
||||
type EstimateActionsProps = {
|
||||
estimateId: string
|
||||
@@ -131,6 +132,7 @@ function SectionHeading({ title }: { title: string }) {
|
||||
export function EstimateActions({ estimateId }: EstimateActionsProps) {
|
||||
const api = useAuthApi()
|
||||
const router = useRouter()
|
||||
const { print, isPrinting } = useDocumentPrint()
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [authOpen, setAuthOpen] = useState(false)
|
||||
const [itemStatuses, setItemStatuses] = useState<Record<string, string>>({})
|
||||
@@ -218,6 +220,10 @@ export function EstimateActions({ estimateId }: EstimateActionsProps) {
|
||||
<Pencil className="size-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => print("estimate", estimateId, "print")} disabled={isPrinting}>
|
||||
<Printer className="size-4" />
|
||||
{isPrinting ? "Printing..." : "Print"}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={openAuthDialog}>
|
||||
<ShieldCheck className="size-4" />
|
||||
Store Authorisation
|
||||
|
||||
@@ -12,10 +12,11 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/components/ui/dropdown-menu"
|
||||
import { Ellipsis, Pencil, Trash2, Play, CheckCircle2 } from "lucide-react"
|
||||
import { Ellipsis, Pencil, Trash2, Play, CheckCircle2, Printer } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import { useFormDialog } from "@/shared/components/form-dialog"
|
||||
import { InspectionForm } from "./inspection-form"
|
||||
import { useDocumentPrint } from "@/shared/hooks/use-document-print"
|
||||
|
||||
type InspectionActionsProps = {
|
||||
inspectionId: string
|
||||
@@ -32,6 +33,7 @@ export function InspectionActions({ inspectionId, status, onStatusChange }: Insp
|
||||
const api = useAuthApi()
|
||||
const router = useRouter()
|
||||
const editDialog = useFormDialog("inspection-details-edit")
|
||||
const { print, isPrinting } = useDocumentPrint()
|
||||
|
||||
const handleDelete = async () => {
|
||||
const promise = api.inspections.destroy(inspectionId)
|
||||
@@ -73,6 +75,10 @@ export function InspectionActions({ inspectionId, status, onStatusChange }: Insp
|
||||
<Pencil className="size-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => print("inspection", inspectionId, "print")} disabled={isPrinting}>
|
||||
<Printer className="size-4" />
|
||||
{isPrinting ? "Printing..." : "Print"}
|
||||
</DropdownMenuItem>
|
||||
{transition && (
|
||||
<DropdownMenuItem onClick={() => handleStatusChange(transition.next)}>
|
||||
<transition.icon className="size-4" />
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -10,10 +9,8 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/components/ui/dropdown-menu"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/shared/components/ui/dialog"
|
||||
import { Ellipsis, Pencil, Trash2 } from "lucide-react"
|
||||
import { InvoiceEditForm } from "./invoice-edit-form"
|
||||
import { useInvoice } from "./invoice-context"
|
||||
import { Ellipsis, Printer, Trash2 } from "lucide-react"
|
||||
import { useDocumentPrint } from "@/shared/hooks/use-document-print"
|
||||
|
||||
type InvoiceActionsProps = {
|
||||
invoiceId: string
|
||||
@@ -22,12 +19,7 @@ type InvoiceActionsProps = {
|
||||
export function InvoiceActions({ invoiceId }: InvoiceActionsProps) {
|
||||
const api = useAuthApi()
|
||||
const router = useRouter()
|
||||
const [isEditOpen, setIsEditOpen] = useState(false)
|
||||
const invoice = useInvoice()
|
||||
const handleEditSuccess = () => {
|
||||
setIsEditOpen(false)
|
||||
router.refresh()
|
||||
}
|
||||
const { print, isPrinting } = useDocumentPrint()
|
||||
|
||||
const handleDelete = async () => {
|
||||
await api.invoices.destroy(invoiceId)
|
||||
@@ -43,31 +35,16 @@ export function InvoiceActions({ invoiceId }: InvoiceActionsProps) {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{/* <DropdownMenuItem onClick={() => setIsEditOpen(true)}>
|
||||
<Pencil className="size-4" />
|
||||
Edit
|
||||
</DropdownMenuItem> */}
|
||||
<DropdownMenuItem onClick={() => print("invoice", invoiceId, "print")} disabled={isPrinting}>
|
||||
<Printer className="size-4" />
|
||||
{isPrinting ? "Printing..." : "Print"}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
||||
<Trash2 className="size-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* <Dialog open={isEditOpen} onOpenChange={setIsEditOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Invoice</DialogTitle>
|
||||
</DialogHeader>
|
||||
<InvoiceEditForm
|
||||
initialData={{
|
||||
|
||||
}}
|
||||
resourceId={invoiceId}
|
||||
onSuccess={handleEditSuccess}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,9 +26,18 @@ import { useJobCard } from "./job-card-context"
|
||||
|
||||
const relationFieldSchema = z.object({ value: z.string(), label: z.string() }).nullable()
|
||||
|
||||
const requiredRelationFieldSchema = relationFieldSchema.superRefine((value, ctx) => {
|
||||
if (!value?.value) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "This field is required",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const jobCardExpenseItemFormSchema = z.object({
|
||||
expense_item: relationFieldSchema,
|
||||
department: relationFieldSchema.optional(),
|
||||
department: requiredRelationFieldSchema,
|
||||
tax: relationFieldSchema.optional(),
|
||||
quantity: z.coerce.number().min(1, "Quantity is required"),
|
||||
rate: z.coerce.number().min(0, "Rate is required"),
|
||||
@@ -62,14 +71,22 @@ const DEFAULT_VALUES: JobCardExpenseItemFormValues = {
|
||||
|
||||
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
|
||||
|
||||
function mapDepartmentOption(item: any) {
|
||||
const id = item?.id ?? item?.department_id
|
||||
return {
|
||||
value: String(id),
|
||||
label: item?.name ?? item?.title ?? item?.department_name ?? String(id),
|
||||
}
|
||||
}
|
||||
|
||||
function mapToFormValues(data: unknown): JobCardExpenseItemFormValues {
|
||||
const d = (data as any) ?? {}
|
||||
return {
|
||||
expense_item: d.expense_item
|
||||
? { value: String(d.expense_item.id), label: d.expense_item.item_name ?? String(d.expense_item.id) }
|
||||
: null,
|
||||
department: d.department
|
||||
? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) }
|
||||
department: (d.department || d.department_id != null)
|
||||
? mapDepartmentOption(d.department ?? { id: d.department_id, name: d.department_name ?? d.department_title })
|
||||
: null,
|
||||
tax: d.tax_id != null
|
||||
? { value: String(d.tax_id), label: formatTaxLabel(d.tax, String(d.tax_id)) }
|
||||
@@ -82,6 +99,18 @@ function mapToFormValues(data: unknown): JobCardExpenseItemFormValues {
|
||||
}
|
||||
}
|
||||
|
||||
function mapJobCardDepartmentToRelation(jobCard: unknown) {
|
||||
const d = (jobCard as any) ?? {}
|
||||
const departmentId = d.department_id ?? d.department?.id
|
||||
if (departmentId == null) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
value: String(departmentId),
|
||||
label: d.department?.name ?? d.department_name ?? d.department_title ?? String(departmentId),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Component ──
|
||||
|
||||
export function JobCardExpenseItemForm({
|
||||
@@ -100,7 +129,10 @@ export function JobCardExpenseItemForm({
|
||||
resolver: zodResolver(jobCardExpenseItemFormSchema) as any,
|
||||
defaultValues: initialData
|
||||
? mapToFormValues(initialData)
|
||||
: DEFAULT_VALUES,
|
||||
: {
|
||||
...DEFAULT_VALUES,
|
||||
department: mapJobCardDepartmentToRelation(jobCard),
|
||||
},
|
||||
})
|
||||
|
||||
const [error, setError] = React.useState<string | null>(null)
|
||||
@@ -126,10 +158,15 @@ export function JobCardExpenseItemForm({
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (!values.department?.value) {
|
||||
setError("Department is required")
|
||||
return
|
||||
}
|
||||
|
||||
await toast.promise(
|
||||
api.jobCards.addExpenseItem(jobCardId, {
|
||||
expense_item_id: values.expense_item ? Number(values.expense_item.value) : undefined,
|
||||
department_id: values.department ? Number(values.department.value) : undefined,
|
||||
department_id: Number(values.department.value),
|
||||
tax_id: values.tax ? Number(values.tax.value) : undefined,
|
||||
quantity: values.quantity,
|
||||
rate: values.rate,
|
||||
@@ -215,12 +252,10 @@ export function JobCardExpenseItemForm({
|
||||
name="department"
|
||||
label="Department"
|
||||
placeholder="Select department"
|
||||
required
|
||||
queryKey={[DEPARTMENT_ROUTES.INDEX]}
|
||||
listFn={() => api.departments.list()}
|
||||
mapOption={(item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name ?? String(item.id),
|
||||
})}
|
||||
mapOption={mapDepartmentOption}
|
||||
createForm={(props) => <DepartmentInlineForm {...props} />}
|
||||
createLabel="Department"
|
||||
{...STORE_OBJECT}
|
||||
|
||||
@@ -118,7 +118,7 @@ function mapToFormValues(data: unknown): JobCardFormValues {
|
||||
customer: toRelation(d.customer_id, d.customer ? `${d.customer.first_name} ${d.customer.last_name}` : undefined),
|
||||
vehicle: toRelation(d.vehicle_id, d.vehicle ? (d.vehicle.plate_number ?? `${d.vehicle.make ?? ""} ${d.vehicle.model ?? ""}`.trim()) : undefined),
|
||||
estimate: toRelation(d.estimate_id, d.estimate?.estimate_number),
|
||||
department: toRelation(d.department_id, d.department?.name),
|
||||
department: toRelation(d.department_id, d.department?.name ?? d.department_name ?? d.department_title),
|
||||
service_writer: toRelation(d.service_writer_id, d.service_writer ? `${d.service_writer.first_name} ${d.service_writer.last_name}` : undefined),
|
||||
primary_technician: toRelation(d.primary_technician_id, d.primary_technician ? `${d.primary_technician.first_name} ${d.primary_technician.last_name}` : undefined),
|
||||
sales_person: toRelation(d.sales_person_id, d.sales_person ? `${d.sales_person.first_name} ${d.sales_person.last_name}` : undefined),
|
||||
@@ -211,11 +211,13 @@ function mapFormToPayload(values: JobCardFormValues) {
|
||||
|
||||
// ── Shared mapOption for async selects ──
|
||||
|
||||
const mapLookupOption = (item: any) => ({
|
||||
const mapLookupOption = (item: any, labelKey: string = "name") => ({
|
||||
value: String(item.id),
|
||||
label: item.name,
|
||||
label: item[labelKey],
|
||||
})
|
||||
|
||||
|
||||
|
||||
const mapEmployeeOption = (item: any) => ({
|
||||
value: String(item.id),
|
||||
label: `${item.first_name} ${item.last_name}`,
|
||||
@@ -320,7 +322,7 @@ export function JobCardForm({ resourceId, initialData, onSuccess }: JobCardFormP
|
||||
placeholder="Select insurance type"
|
||||
queryKey={[INSURANCE_TYPE_ROUTES.INDEX]}
|
||||
listFn={() => api.insuranceTypes.list()}
|
||||
mapOption={mapLookupOption}
|
||||
mapOption={(op) => (mapLookupOption(op, 'title'))}
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
</div>
|
||||
@@ -341,7 +343,7 @@ export function JobCardForm({ resourceId, initialData, onSuccess }: JobCardFormP
|
||||
label="Service Writer"
|
||||
placeholder="Select service writer"
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
/>
|
||||
<RhfEmployeeSelectField
|
||||
showCreate
|
||||
name="primary_technician"
|
||||
@@ -436,6 +438,7 @@ export function JobCardForm({ resourceId, initialData, onSuccess }: JobCardFormP
|
||||
name="department"
|
||||
label="Department"
|
||||
placeholder="Select department"
|
||||
required
|
||||
queryKey={[DEPARTMENT_ROUTES.INDEX]}
|
||||
listFn={() => api.departments.list()}
|
||||
mapOption={mapLookupOption}
|
||||
|
||||
@@ -62,14 +62,22 @@ const DEFAULT_VALUES: JobCardPartFormValues = {
|
||||
|
||||
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
|
||||
|
||||
function mapDepartmentOption(item: any) {
|
||||
const id = item?.id ?? item?.department_id
|
||||
return {
|
||||
value: String(id),
|
||||
label: item?.name ?? item?.title ?? item?.department_name ?? String(id),
|
||||
}
|
||||
}
|
||||
|
||||
function mapToFormValues(data: unknown): JobCardPartFormValues {
|
||||
const d = (data as any) ?? {}
|
||||
return {
|
||||
part: d.part
|
||||
? { value: String(d.part.id), label: d.part.title ?? String(d.part.id) }
|
||||
: null,
|
||||
department: d.department
|
||||
? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) }
|
||||
department: (d.department || d.department_id != null)
|
||||
? mapDepartmentOption(d.department ?? { id: d.department_id, name: d.department_name ?? d.department_title })
|
||||
: null,
|
||||
tax: d.tax_id != null
|
||||
? { value: String(d.tax_id), label: formatTaxLabel(d.tax, String(d.tax_id)) }
|
||||
@@ -82,6 +90,18 @@ function mapToFormValues(data: unknown): JobCardPartFormValues {
|
||||
}
|
||||
}
|
||||
|
||||
function mapJobCardDepartmentToRelation(jobCard: unknown) {
|
||||
const d = (jobCard as any) ?? {}
|
||||
const departmentId = d.department_id ?? d.department?.id
|
||||
if (departmentId == null) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
value: String(departmentId),
|
||||
label: d.department?.name ?? d.department_name ?? d.department_title ?? String(departmentId),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Component ──
|
||||
|
||||
export function JobCardPartForm({
|
||||
@@ -100,7 +120,10 @@ export function JobCardPartForm({
|
||||
resolver: zodResolver(jobCardPartFormSchema) as any,
|
||||
defaultValues: initialData
|
||||
? mapToFormValues(initialData)
|
||||
: DEFAULT_VALUES,
|
||||
: {
|
||||
...DEFAULT_VALUES,
|
||||
department: mapJobCardDepartmentToRelation(jobCard),
|
||||
},
|
||||
})
|
||||
|
||||
const [error, setError] = React.useState<string | null>(null)
|
||||
@@ -126,10 +149,15 @@ export function JobCardPartForm({
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (!values.department?.value) {
|
||||
setError("Department is required")
|
||||
return
|
||||
}
|
||||
|
||||
await toast.promise(
|
||||
api.jobCards.addPart(jobCardId, {
|
||||
part_id: values.part ? Number(values.part.value) : undefined,
|
||||
department_id: values.department ? Number(values.department.value) : undefined,
|
||||
department_id: Number(values.department.value),
|
||||
tax_id: values.tax ? Number(values.tax.value) : undefined,
|
||||
quantity: values.quantity,
|
||||
rate: values.rate,
|
||||
@@ -215,12 +243,10 @@ export function JobCardPartForm({
|
||||
name="department"
|
||||
label="Department"
|
||||
placeholder="Select department"
|
||||
required
|
||||
queryKey={[DEPARTMENT_ROUTES.INDEX]}
|
||||
listFn={() => api.departments.list()}
|
||||
mapOption={(item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name ?? String(item.id),
|
||||
})}
|
||||
mapOption={mapDepartmentOption}
|
||||
createForm={(props) => <DepartmentInlineForm {...props} />}
|
||||
createLabel="Department"
|
||||
{...STORE_OBJECT}
|
||||
|
||||
@@ -71,14 +71,22 @@ const DEFAULT_VALUES: JobCardServiceFormValues = {
|
||||
|
||||
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
|
||||
|
||||
function mapDepartmentOption(item: any) {
|
||||
const id = item?.id ?? item?.department_id
|
||||
return {
|
||||
value: String(id),
|
||||
label: item?.name ?? item?.title ?? item?.department_name ?? String(id),
|
||||
}
|
||||
}
|
||||
|
||||
function mapToFormValues(data: unknown): JobCardServiceFormValues {
|
||||
const d = (data as any) ?? {}
|
||||
return {
|
||||
service: d.service
|
||||
? { value: String(d.service.id), label: d.service.labor_name ?? String(d.service.id) }
|
||||
: null,
|
||||
department: d.department
|
||||
? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) }
|
||||
department: (d.department || d.department_id != null)
|
||||
? mapDepartmentOption(d.department ?? { id: d.department_id, name: d.department_name ?? d.department_title })
|
||||
: undefined as any,
|
||||
tax: d.tax_id != null
|
||||
? { value: String(d.tax_id), label: formatTaxLabel(d.tax, String(d.tax_id)) }
|
||||
@@ -97,6 +105,18 @@ function mapToFormValues(data: unknown): JobCardServiceFormValues {
|
||||
}
|
||||
}
|
||||
|
||||
function mapJobCardDepartmentToRelation(jobCard: unknown) {
|
||||
const d = (jobCard as any) ?? {}
|
||||
const departmentId = d.department_id ?? d.department?.id
|
||||
if (departmentId == null) {
|
||||
return undefined as any
|
||||
}
|
||||
return {
|
||||
value: String(departmentId),
|
||||
label: d.department?.name ?? d.department_name ?? d.department_title ?? String(departmentId),
|
||||
}
|
||||
}
|
||||
|
||||
const RATE_TYPE_OPTIONS = RateType.map((v) => ({
|
||||
value: v,
|
||||
label: v === "flat_rate" ? "Flat Rate" : "Hourly",
|
||||
@@ -120,7 +140,10 @@ export function JobCardServiceForm({
|
||||
resolver: zodResolver(jobCardServiceFormSchema) as any,
|
||||
defaultValues: initialData
|
||||
? mapToFormValues(initialData)
|
||||
: DEFAULT_VALUES,
|
||||
: {
|
||||
...DEFAULT_VALUES,
|
||||
department: mapJobCardDepartmentToRelation(jobCard),
|
||||
},
|
||||
})
|
||||
|
||||
const rateType = form.watch("rate_type")
|
||||
@@ -149,10 +172,15 @@ export function JobCardServiceForm({
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (!values.department?.value) {
|
||||
setError("Department is required")
|
||||
return
|
||||
}
|
||||
|
||||
await toast.promise(
|
||||
api.jobCards.addService(jobCardId, {
|
||||
service_id: values.service ? Number(values.service.value) : undefined,
|
||||
department_id: values.department ? Number(values.department.value) : undefined,
|
||||
department_id: Number(values.department.value),
|
||||
tax_id: values.tax ? Number(values.tax.value) : undefined,
|
||||
rate_type: values.rate_type || undefined,
|
||||
labor_rate_id: values.labor_rate ? Number(values.labor_rate.value) : undefined,
|
||||
@@ -283,10 +311,7 @@ export function JobCardServiceForm({
|
||||
required
|
||||
queryKey={[DEPARTMENT_ROUTES.INDEX]}
|
||||
listFn={() => api.departments.list()}
|
||||
mapOption={(item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name ?? String(item.id),
|
||||
})}
|
||||
mapOption={mapDepartmentOption}
|
||||
createForm={(props) => <DepartmentInlineForm {...props} />}
|
||||
createLabel="Department"
|
||||
{...STORE_OBJECT}
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/components/ui/dropdown-menu"
|
||||
import { confirm } from "@/shared/components/confirm-dialog"
|
||||
import { Ellipsis, Pencil, Trash2 } from "lucide-react"
|
||||
import { Ellipsis, Printer, Trash2 } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import { useDocumentPrint } from "@/shared/hooks/use-document-print"
|
||||
|
||||
type PurchaseOrderActionsProps = {
|
||||
purchaseOrderId: string
|
||||
@@ -20,6 +21,7 @@ type PurchaseOrderActionsProps = {
|
||||
export function PurchaseOrderActions({ purchaseOrderId }: PurchaseOrderActionsProps) {
|
||||
const api = useAuthApi()
|
||||
const router = useRouter()
|
||||
const { print, isPrinting } = useDocumentPrint()
|
||||
|
||||
const handleDelete = async () => {
|
||||
const confirmed = await confirm({
|
||||
@@ -48,6 +50,10 @@ export function PurchaseOrderActions({ purchaseOrderId }: PurchaseOrderActionsPr
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => print("purchase_order", purchaseOrderId, "print")} disabled={isPrinting}>
|
||||
<Printer className="size-4" />
|
||||
{isPrinting ? "Printing..." : "Print"}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
||||
<Trash2 className="size-4" />
|
||||
Delete
|
||||
|
||||
Reference in New Issue
Block a user