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
@@ -37,6 +37,14 @@ export default function JobCardDropdown({ id }: { id: string }) {
router.push(`/sales/job-cards/${id}/appointments?create=1`)
}
const existingInvoice = (jobCard as any)?.invoices?.[0] as { id: number | string; invoice_number?: string | null } | undefined
const handleOpenExistingInvoice = () => {
if (existingInvoice?.id != null) {
router.push(`/sales/invoice/${existingInvoice.id}`)
}
}
const handleConvertToInvoice = async () => {
const confirmed = await confirm({
title: "Convert to Invoice",
@@ -95,10 +103,17 @@ export default function JobCardDropdown({ id }: { id: string }) {
return (
<div className="flex items-center gap-2">
{jobCard?.status !== "draft" && (
<Button variant="outline" onClick={handleConvertToInvoice} disabled={isConverting}>
{isConverting ? <Loader2 className="size-4 animate-spin" /> : <FileText className="size-4" />}
{isConverting ? "Converting..." : "Convert to Invoice"}
</Button>
existingInvoice ? (
<Button variant="outline" onClick={handleOpenExistingInvoice}>
<FileText className="size-4" />
{existingInvoice.invoice_number ? `Invoice #${existingInvoice.invoice_number}` : "View Invoice"}
</Button>
) : (
<Button variant="outline" onClick={handleConvertToInvoice} disabled={isConverting}>
{isConverting ? <Loader2 className="size-4 animate-spin" /> : <FileText className="size-4" />}
{isConverting ? "Converting..." : "Convert to Invoice"}
</Button>
)
)}
<Button variant="outline" onClick={handleCreateAppointment}>
@@ -133,10 +148,17 @@ export default function JobCardDropdown({ id }: { id: string }) {
Create Appointment
</DropdownMenuItem>
{jobCard?.status !== "draft" && (
<DropdownMenuItem onClick={handleConvertToInvoice} disabled={isConverting}>
<FileText className="size-4" />
{isConverting ? "Converting..." : "Convert to Invoice"}
</DropdownMenuItem>
existingInvoice ? (
<DropdownMenuItem onClick={handleOpenExistingInvoice}>
<FileText className="size-4" />
{existingInvoice.invoice_number ? `Invoice #${existingInvoice.invoice_number}` : "View Invoice"}
</DropdownMenuItem>
) : (
<DropdownMenuItem onClick={handleConvertToInvoice} disabled={isConverting}>
<FileText className="size-4" />
{isConverting ? "Converting..." : "Convert to Invoice"}
</DropdownMenuItem>
)
)}
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
@@ -16,8 +16,9 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/shared/components/ui/collapsible"
import { Button } from "@/shared/components/ui/button"
import { useJobCard } from "./job-card-context"
import { formatDate, formatCurrency } from "@/shared/utils/formatters"
import { formatDate } from "@/shared/utils/formatters"
import { useState } from "react"
import { Money } from "@/shared/components/money"
export default function JobCardPaymentsReceived() {
const jobCard = useJobCard()
@@ -88,7 +89,7 @@ export default function JobCardPaymentsReceived() {
<div className="flex items-center gap-2">
<BadgeDollarSignIcon className="h-4 w-4 text-emerald-600" />
<span className="font-semibold text-emerald-700 dark:text-emerald-400">
{formatCurrency(item.amount_received)}
{<Money value={item.amount_received} />}
</span>
</div>
)