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
@@ -20,8 +20,9 @@ import {
} from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { cn } from "@/shared/lib/utils"
import { formatDate, formatCurrency, formatEnum, formatTaxLabel } from "@/shared/utils/formatters"
import { formatDate, formatEnum, formatTaxLabel } from "@/shared/utils/formatters"
import { useExpense } from "./expense-context"
import { Money } from "@/shared/components/money"
function InfoItem({
icon: Icon,
@@ -30,7 +31,7 @@ function InfoItem({
}: {
icon: React.ComponentType<{ className?: string }>
label: string
value?: string | null
value?: React.ReactNode
}) {
return (
<div className="flex items-start gap-3">
@@ -92,10 +93,10 @@ export function ExpenseGeneralInfo() {
<Card className="flex flex-col gap-1 p-4">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Total</span>
<span className="mt-1 text-lg font-semibold">
{formatCurrency(expense.total ?? 0)}
{<Money value={expense.total ?? 0} />}
</span>
{expense.sub_total != null && expense.sub_total !== expense.total && (
<span className="text-xs text-muted-foreground">Subtotal: {formatCurrency(expense.sub_total)}</span>
<span className="text-xs text-muted-foreground">Subtotal: {<Money value={expense.sub_total} />}</span>
)}
</Card>
@@ -103,7 +104,7 @@ export function ExpenseGeneralInfo() {
<Card className="flex flex-col gap-1 p-4">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Paid</span>
<span className="mt-1 text-lg font-semibold text-green-600 dark:text-green-400">
{formatCurrency(paymentsM ?? 0)}
{<Money value={paymentsM ?? 0} />}
</span>
</Card>
@@ -111,7 +112,7 @@ export function ExpenseGeneralInfo() {
<Card className={cn("flex flex-col gap-1 p-4", (balanceDue ?? 0) > 0 && "border-destructive/50 bg-destructive/5")}>
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Balance Due</span>
<span className={cn("mt-1 text-lg font-semibold", (balanceDue ?? 0) > 0 ? "text-destructive" : "text-green-600 dark:text-green-400")}>
{formatCurrency(balanceDue ?? 0)}
{<Money value={balanceDue ?? 0} />}
</span>
</Card>
</div>
@@ -139,10 +140,10 @@ export function ExpenseGeneralInfo() {
<InfoItem icon={Percent} label="Discount Type" value={formatEnum(expense.discount)} />
)}
{expense.discount_amount_major != null && expense.discount_amount_major > 0 && (
<InfoItem icon={Percent} label="Discount" value={formatCurrency(expense.discount_amount_major)} />
<InfoItem icon={Percent} label="Discount" value={<Money value={expense.discount_amount_major} />} />
)}
{expense.tax_amount != null && expense.tax_amount > 0 && (
<InfoItem icon={Percent} label={taxLabel} value={formatCurrency(expense.tax_amount)} />
<InfoItem icon={Percent} label={taxLabel} value={<Money value={expense.tax_amount} />} />
)}
</div>
</CardContent>
@@ -10,7 +10,7 @@ import {
TableHeader,
TableRow,
} from "@/shared/components/ui/table"
import { formatCurrency } from "@/shared/utils/formatters"
import { Money } from "@/shared/components/money"
type ExpenseItem = {
id?: number
@@ -89,8 +89,8 @@ export function ExpenseItemsSection({
{item.description || "—"}
</TableCell>
<TableCell className="text-right text-sm">{qty.toLocaleString()}</TableCell>
<TableCell className="text-right text-sm">{formatCurrency(rate)}</TableCell>
<TableCell className="text-right font-semibold">{formatCurrency(amount)}</TableCell>
<TableCell className="text-right text-sm">{<Money value={rate} />}</TableCell>
<TableCell className="text-right font-semibold">{<Money value={amount} />}</TableCell>
</TableRow>
)
})}
@@ -104,25 +104,25 @@ export function ExpenseItemsSection({
{subTotal != null && (
<div className="flex justify-between">
<span className="text-muted-foreground">Subtotal</span>
<span className="font-medium">{formatCurrency(subTotal)}</span>
<span className="font-medium">{<Money value={subTotal} />}</span>
</div>
)}
{discountAmount != null && discountAmount > 0 && discountType !== "no" && (
<div className="flex justify-between text-destructive">
<span>Discount</span>
<span>− {formatCurrency(discountAmount)}</span>
<span>− {<Money value={discountAmount} />}</span>
</div>
)}
{taxAmount != null && taxAmount > 0 && (
<div className="flex justify-between">
<span className="text-muted-foreground">{taxLabel || "Tax"}</span>
<span className="font-medium">{formatCurrency(taxAmount)}</span>
<span className="font-medium">{<Money value={taxAmount} />}</span>
</div>
)}
{total != null && (
<div className="flex justify-between border-t pt-2 text-base font-semibold">
<span>Total</span>
<span>{formatCurrency(total)}</span>
<span>{<Money value={total} />}</span>
</div>
)}
</div>