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
@@ -15,7 +15,8 @@ import {
TableHeader,
TableRow,
} from "@/shared/components/ui/table"
import { formatCurrency, formatNumber } from "@/shared/utils/formatters"
import { formatNumber } from "@/shared/utils/formatters"
import { Money } from "@/shared/components/money"
type InvoiceExpense = {
id: number
@@ -78,10 +79,10 @@ export function InvoiceExpensesSection({ expenses = [] }: InvoiceExpensesSection
{formatNumber(qty)}
</TableCell>
<TableCell className="text-right">
{formatCurrency(rate)}
{<Money value={rate} />}
</TableCell>
<TableCell className="text-right font-medium">
{formatCurrency(amount)}
{<Money value={amount} />}
</TableCell>
</TableRow>
)
@@ -91,7 +92,7 @@ export function InvoiceExpensesSection({ expenses = [] }: InvoiceExpensesSection
Subtotal
</TableCell>
<TableCell className="text-right">
{formatCurrency(subtotal)}
{<Money value={subtotal} />}
</TableCell>
</TableRow>
</TableBody>
@@ -22,8 +22,9 @@ import {
import { Badge } from "@/shared/components/ui/badge"
import { Separator } from "@/shared/components/ui/separator"
import { cn } from "@/shared/lib/utils"
import { formatDate, formatCurrency, formatEnum, formatNumber } from "@/shared/utils/formatters"
import { formatDate, formatEnum, formatNumber } from "@/shared/utils/formatters"
import { useInvoice } from "./invoice-context"
import { Money } from "@/shared/components/money"
function InfoItem({
icon: Icon,
@@ -140,9 +141,9 @@ export function InvoiceGeneralInfo() {
{/* Total Amount */}
<Card className="flex flex-col gap-1 p-4">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Total Amount</span>
<span className="mt-1 text-lg font-semibold">{formatCurrency(total)}</span>
<span className="mt-1 text-lg font-semibold">{<Money value={total} />}</span>
{paid > 0 && (
<span className="text-xs text-muted-foreground">{formatCurrency(paid)} received</span>
<span className="text-xs text-muted-foreground">{<Money value={paid} />} received</span>
)}
</Card>
@@ -158,7 +159,7 @@ export function InvoiceGeneralInfo() {
balanceDue > 0 && invoice.status !== "paid" && "text-primary",
balanceDue <= 0 && "text-green-600",
)}>
{formatCurrency(balanceDue)}
{<Money value={balanceDue} />}
</span>
{balanceDue <= 0 && (
<span className="text-xs font-medium text-green-600">Fully paid</span>
@@ -15,7 +15,8 @@ import {
TableHeader,
TableRow,
} from "@/shared/components/ui/table"
import { formatCurrency, formatNumber } from "@/shared/utils/formatters"
import { formatNumber } from "@/shared/utils/formatters"
import { Money } from "@/shared/components/money"
type InvoicePart = {
id: number
@@ -78,10 +79,10 @@ export function InvoicePartsSection({ parts = [] }: InvoicePartsSectionProps) {
{formatNumber(qty)}
</TableCell>
<TableCell className="text-right">
{formatCurrency(rate)}
{<Money value={rate} />}
</TableCell>
<TableCell className="text-right font-medium">
{formatCurrency(amount)}
{<Money value={amount} />}
</TableCell>
</TableRow>
)
@@ -91,7 +92,7 @@ export function InvoicePartsSection({ parts = [] }: InvoicePartsSectionProps) {
Subtotal
</TableCell>
<TableCell className="text-right">
{formatCurrency(subtotal)}
{<Money value={subtotal} />}
</TableCell>
</TableRow>
</TableBody>
@@ -27,25 +27,37 @@ export default function InvoicePaymentsSection() {
<CardContent className='flex justify-between '>
<h2>Payments</h2>
<FormDialog title="Record Payment">
{(resourceId) => (
<PaymentReceivedForm
invoiceId={invoice?.id as string}
invoiceCustomer={
(invoice?.customer as any) ??
((invoice as any)?.customer_id
? {
id: (invoice as any).customer_id,
first_name: (invoice as any).customer_name,
}
: null)
}
invoiceAmount={invoice?.balance_due as any}
resourceId={resourceId}
onSuccess={()=>{router.refresh(); invalidateQuery()}}
/>
)}
{(resourceId) => {
const invoiceJobCard =
(invoice as any)?.jobCard ?? (invoice as any)?.job_card ?? null
const invoiceJobCardId =
invoiceJobCard?.id ?? (invoice as any)?.job_card_id ?? null
const invoiceJobCardTitle =
invoiceJobCard?.title ?? (invoice as any)?.job_card_name ?? null
return (
<PaymentReceivedForm
invoiceId={invoice?.id as string}
invoiceCustomer={
(invoice?.customer as any) ??
((invoice as any)?.customer_id
? {
id: (invoice as any).customer_id,
first_name: (invoice as any).customer_name,
}
: null)
}
invoiceAmount={invoice?.balance_due as any}
defaultJobCard={
invoiceJobCardId != null
? { id: Number(invoiceJobCardId), title: invoiceJobCardTitle }
: null
}
lockJobCard={false}
resourceId={resourceId}
onSuccess={() => { router.refresh(); invalidateQuery() }}
/>
)
}}
</FormDialog>
</CardContent>
@@ -15,7 +15,8 @@ import {
TableHeader,
TableRow,
} from "@/shared/components/ui/table"
import { formatCurrency, formatNumber } from "@/shared/utils/formatters"
import { formatNumber } from "@/shared/utils/formatters"
import { Money } from "@/shared/components/money"
type InvoiceService = {
id: number
@@ -78,10 +79,10 @@ export function InvoiceServicesSection({ services = [] }: InvoiceServicesSection
{formatNumber(qty)}
</TableCell>
<TableCell className="text-right">
{formatCurrency(rate)}
{<Money value={rate} />}
</TableCell>
<TableCell className="text-right font-medium">
{formatCurrency(amount)}
{<Money value={amount} />}
</TableCell>
</TableRow>
)
@@ -91,7 +92,7 @@ export function InvoiceServicesSection({ services = [] }: InvoiceServicesSection
Subtotal
</TableCell>
<TableCell className="text-right">
{formatCurrency(subtotal)}
{<Money value={subtotal} />}
</TableCell>
</TableRow>
</TableBody>
@@ -1,9 +1,10 @@
"use client"
import { Card, CardContent } from "@/shared/components/ui/card"
import { Separator } from "@/shared/components/ui/separator"
import { formatCurrency, formatEnum } from "@/shared/utils/formatters"
import { formatEnum } from "@/shared/utils/formatters"
import { cn } from "@/shared/lib/utils"
import { useInvoice } from "./invoice-context"
import { Money } from "@/shared/components/money"
export function InvoiceTotalsSummary() {
const invoice = useInvoice()
@@ -41,19 +42,19 @@ export function InvoiceTotalsSummary() {
{parts.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Parts ({parts.length})</span>
<span>{formatCurrency(lineTotal(parts))}</span>
<span>{<Money value={lineTotal(parts)} />}</span>
</div>
)}
{services.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Services ({services.length})</span>
<span>{formatCurrency(lineTotal(services))}</span>
<span>{<Money value={lineTotal(services)} />}</span>
</div>
)}
{expenses.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Expenses ({expenses.length})</span>
<span>{formatCurrency(lineTotal(expenses))}</span>
<span>{<Money value={lineTotal(expenses)} />}</span>
</div>
)}
<Separator />
@@ -62,7 +63,7 @@ export function InvoiceTotalsSummary() {
<div className="flex justify-between text-sm font-medium">
<span>Subtotal</span>
<span>{formatCurrency(subTotal)}</span>
<span>{<Money value={subTotal} />}</span>
</div>
{discount && discount !== "no" && (
@@ -76,13 +77,13 @@ export function InvoiceTotalsSummary() {
<div className="flex justify-between text-base font-semibold">
<span>Total</span>
<span>{formatCurrency(displayTotal)}</span>
<span>{<Money value={displayTotal} />}</span>
</div>
{paid > 0 && (
<div className="flex justify-between text-sm text-muted-foreground">
<span>Amount Received</span>
<span>– {formatCurrency(paid)}</span>
<span>– {<Money value={paid} />}</span>
</div>
)}
@@ -93,7 +94,7 @@ export function InvoiceTotalsSummary() {
balanceDue > 0 ? "bg-primary/10 text-primary" : "bg-green-500/10 text-green-600",
)}>
<span>Balance Due</span>
<span>{formatCurrency(balanceDue)}</span>
<span>{<Money value={balanceDue} />}</span>
</div>
</div>
</CardContent>