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
@@ -3,7 +3,8 @@
import { Receipt } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Table, TableBody, TableCell, TableHead, 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 BillExpense = {
id: number
@@ -62,14 +63,14 @@ export function BillExpensesSection({ expenses = [] }: BillExpensesSectionProps)
{expense.description || "—"}
</TableCell>
<TableCell className="text-right">{formatNumber(qty)}</TableCell>
<TableCell className="text-right">{formatCurrency(rate)}</TableCell>
<TableCell className="text-right font-medium">{formatCurrency(amount)}</TableCell>
<TableCell className="text-right">{<Money value={rate} />}</TableCell>
<TableCell className="text-right font-medium">{<Money value={amount} />}</TableCell>
</TableRow>
)
})}
<TableRow className="bg-muted/50 font-medium">
<TableCell colSpan={4} className="text-right">Subtotal</TableCell>
<TableCell className="text-right">{formatCurrency(subtotal)}</TableCell>
<TableCell className="text-right">{<Money value={subtotal} />}</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -19,9 +19,10 @@ import {
} from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { cn } from "@/shared/lib/utils"
import { formatDate, formatCurrency, formatEnum } from "@/shared/utils/formatters"
import { formatDate, formatEnum } from "@/shared/utils/formatters"
import { getFullName } from "@/shared/utils/getFullName"
import { useBill } from "./bill-context"
import { Money } from "@/shared/components/money"
function InfoItem({
icon: Icon,
@@ -3,7 +3,8 @@
import { Wrench } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Table, TableBody, TableCell, TableHead, 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 BillPart = {
id: number
@@ -62,14 +63,14 @@ export function BillPartsSection({ parts = [] }: BillPartsSectionProps) {
{part.description || "—"}
</TableCell>
<TableCell className="text-right">{formatNumber(qty)}</TableCell>
<TableCell className="text-right">{formatCurrency(rate)}</TableCell>
<TableCell className="text-right font-medium">{formatCurrency(amount)}</TableCell>
<TableCell className="text-right">{<Money value={rate} />}</TableCell>
<TableCell className="text-right font-medium">{<Money value={amount} />}</TableCell>
</TableRow>
)
})}
<TableRow className="bg-muted/50 font-medium">
<TableCell colSpan={4} className="text-right">Subtotal</TableCell>
<TableCell className="text-right">{formatCurrency(subtotal)}</TableCell>
<TableCell className="text-right">{<Money value={subtotal} />}</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -17,7 +17,6 @@ import { PaymentMadeForm } from "@/modules/payment-mades/payment-made-form"
import { useBill } from "./bill-context"
import { formatDate } from "@/shared/utils/formatters"
import { getFullName } from "@/shared/utils/getFullName"
import { toRelation } from "@/shared/lib/utils"
export function BillPaymentsSection() {
const bill = useBill()
@@ -39,8 +38,17 @@ export function BillPaymentsSection() {
{(resourceId) => (
<PaymentMadeForm
initialData={{
amount: bill?.balance_due,
vendor: toRelation(bill?.vendor?.id, getFullName(bill?.vendor as any)),
// API-shape keys so PaymentMadeForm.mapToFormValues
// can pick them up (it reads `payment_made` / `vendor_id`
// / `vendor.*`, not the form-shape fields). bill_id +
// bill_number are required because the bill field is
// hidden when billId is supplied, but the schema still
// demands a populated bill relation.
bill_id: bill?.id,
bill_number: bill?.bill_number,
payment_made: bill?.balance_due,
vendor_id: bill?.vendor?.id,
vendor: bill?.vendor,
payment_for: "bill",
}}
billId={bill?.id}
@@ -77,7 +85,7 @@ export function BillPaymentsSection() {
return (
<div className="flex items-center gap-2">
<UserIcon className="h-4 w-4 text-muted-foreground" />
<span>{item.vendor?.name || item.vendor_name || "—"}</span>
<span>{item.vendor?.company_name || getFullName(item.vendor) || item.vendor_name || "—"}</span>
</div>
)
},
@@ -3,7 +3,8 @@
import { Briefcase } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Table, TableBody, TableCell, TableHead, 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 BillService = {
id: number
@@ -62,14 +63,14 @@ export function BillServicesSection({ services = [] }: BillServicesSectionProps)
{service.description || "—"}
</TableCell>
<TableCell className="text-right">{formatNumber(qty)}</TableCell>
<TableCell className="text-right">{formatCurrency(rate)}</TableCell>
<TableCell className="text-right font-medium">{formatCurrency(amount)}</TableCell>
<TableCell className="text-right">{<Money value={rate} />}</TableCell>
<TableCell className="text-right font-medium">{<Money value={amount} />}</TableCell>
</TableRow>
)
})}
<TableRow className="bg-muted/50 font-medium">
<TableCell colSpan={4} className="text-right">Subtotal</TableCell>
<TableCell className="text-right">{formatCurrency(subtotal)}</TableCell>
<TableCell className="text-right">{<Money value={subtotal} />}</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -2,9 +2,10 @@
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 { useBill } from "./bill-context"
import { Money } from "@/shared/components/money"
export function BillTotalsSummary() {
const bill = useBill()
@@ -46,70 +47,65 @@ export function BillTotalsSummary() {
{parts.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Parts ({parts.length})</span>
<span>{formatCurrency(partsTotal)}</span>
<span>{<Money value={partsTotal} />}</span>
</div>
)}
{services.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Services ({services.length})</span>
<span>{formatCurrency(servicesTotal)}</span>
<span>{<Money value={servicesTotal} />}</span>
</div>
)}
{expenses.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Expenses ({expenses.length})</span>
<span>{formatCurrency(expensesTotal)}</span>
<span>{<Money value={expensesTotal} />}</span>
</div>
)}
<Separator />
<div className="flex justify-between text-sm font-medium">
<span>Subtotal</span>
<span>{formatCurrency(subTotal)}</span>
<span>{<Money value={subTotal} />}</span>
</div>
{discountAmount > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Discount{discount && discount !== "no" ? ` (${formatEnum(discount)})` : ""}</span>
<span className="text-muted-foreground">−{formatCurrency(discountAmount)}</span>
<span className="text-muted-foreground">−{<Money value={discountAmount} />}</span>
</div>
)}
{taxAmount > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Tax{bill.tax?.name ? ` (${bill.tax.name})` : ""}</span>
<span>{formatCurrency(taxAmount)}</span>
<span>{<Money value={taxAmount} />}</span>
</div>
)}
<Separator />
<div className="flex justify-between text-base font-semibold">
<span>Total</span>
<span>{<Money value={total} />}</span>
</div>
{paymentsMade > 0 && (
<div className="flex justify-between text-sm text-muted-foreground">
<span>Amount Paid</span>
<span>– {<Money value={paymentsMade} />}</span>
</div>
)}
<Separator />
<div className={cn(
"flex justify-between rounded-lg px-3 py-2 text-base font-bold bg-muted/50",
"flex justify-between rounded-lg px-3 py-2 text-base font-bold",
balanceDue > 0 ? "bg-primary/10 text-primary" : "bg-green-500/10 text-green-600",
)}>
<span>Total</span>
<span>{formatCurrency(total)}</span>
<span>Balance Due</span>
<span>{<Money value={balanceDue} />}</span>
</div>
{paymentsMade > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Payments Made</span>
<span className="text-emerald-600">−{formatCurrency(paymentsMade)}</span>
</div>
)}
{paymentsMade > 0 && (
<>
<Separator />
<div className={cn(
"flex justify-between rounded-lg px-3 py-2 text-base font-semibold",
balanceDue > 0 ? "text-destructive bg-destructive/5" : "text-emerald-700 bg-emerald-50 dark:bg-emerald-950/20",
)}>
<span>Balance Due</span>
<span>{formatCurrency(balanceDue)}</span>
</div>
</>
)}
</div>
</CardContent>
</Card>