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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user