fix build

This commit is contained in:
Mohammad Khyata 2026-04-23 15:15:21 +03:00
parent c0f78c6e18
commit 564e9e510f
13 changed files with 85 additions and 17 deletions

View File

@ -2,6 +2,7 @@ import DashboardPage from '@/base/components/layout/dashboard/dashboard-page'
import { ExpenseGeneralInfo } from '@/modules/expenses/expense-general-info' import { ExpenseGeneralInfo } from '@/modules/expenses/expense-general-info'
import { ExpenseItemsSection } from '@/modules/expenses/expense-items-section' import { ExpenseItemsSection } from '@/modules/expenses/expense-items-section'
import { ExpensePaymentsSection } from '@/modules/expenses/expense-payments-section' import { ExpensePaymentsSection } from '@/modules/expenses/expense-payments-section'
import { formatTaxLabel } from '@/shared/utils/formatters'
import { getServerApi } from '@garage/api/server' import { getServerApi } from '@garage/api/server'
export default async function ExpenseDetailPage(props: { params: Promise<{ id: string }> }) { export default async function ExpenseDetailPage(props: { params: Promise<{ id: string }> }) {
@ -14,7 +15,7 @@ export default async function ExpenseDetailPage(props: { params: Promise<{ id: s
return <div className="text-muted-foreground">Expense not found.</div> return <div className="text-muted-foreground">Expense not found.</div>
} }
const taxLabel = data.tax?.title ? `${data.tax.title} (${data.tax.rate}%)` : undefined const taxLabel = formatTaxLabel(data.tax, '') || undefined
return ( return (
<DashboardPage header={null}> <DashboardPage header={null}>

View File

@ -17,7 +17,7 @@ export const expenseItemFormSchema = z.object({
purchase_information: z.boolean().default(true), purchase_information: z.boolean().default(true),
purchase_price: z.coerce.number().min(0).optional(), purchase_price: z.coerce.number().min(0).optional(),
purchase_chart_of_account: z.string().optional(), purchase_chart_of_account: z.string().optional(),
// purchase_preferred_vendor: relationFieldSchema, purchase_preferred_vendor: relationFieldSchema,
// Sales // Sales
sales_information: z.boolean().default(false), sales_information: z.boolean().default(false),
selling_price: z.coerce.number().min(0).optional(), selling_price: z.coerce.number().min(0).optional(),

View File

@ -3,7 +3,47 @@
import { CrudShowResponse, ExpensesClient } from "@garage/api" import { CrudShowResponse, ExpensesClient } from "@garage/api"
import { createContext, useContext } from "react" import { createContext, useContext } from "react"
export type ExpenseContextValue = CrudShowResponse<ExpensesClient>['data'] type BaseExpenseContextValue = NonNullable<CrudShowResponse<ExpensesClient>["data"]>
type ExpenseNamedRelation = {
id?: number
name?: string | null
title?: string | null
}
type ExpenseVendorRelation = ExpenseNamedRelation & {
company_name?: string | null
first_name?: string | null
last_name?: string | null
}
type ExpenseJobCardRelation = {
id?: number
order_number?: string | null
estimate_number?: string | null
title?: string | null
}
type ExpenseTaxRelation = ExpenseNamedRelation & {
rate?: string | number | null
}
type ExpenseLabel = {
id?: number
title?: string | null
color_code?: string | null
}
export type ExpenseContextValue = BaseExpenseContextValue & {
discount?: string | null
discount_amount_major?: number | null
vendor?: ExpenseVendorRelation | null
department?: ExpenseNamedRelation | null
category?: ExpenseNamedRelation | null
job_card?: ExpenseJobCardRelation | null
tax?: ExpenseTaxRelation | null
labels?: ExpenseLabel[] | null
}
const ExpenseContext = createContext<ExpenseContextValue | null>(null) const ExpenseContext = createContext<ExpenseContextValue | null>(null)

View File

@ -19,6 +19,7 @@ import { useAuthApi } from "@/shared/useApi"
import { useResourceForm } from "@/shared/hooks/use-resource-form" import { useResourceForm } from "@/shared/hooks/use-resource-form"
import { useFormMutation } from "@/shared/hooks/use-form-mutation" import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { getTodayDate, toRelation, toId } from "@/shared/lib/utils" import { getTodayDate, toRelation, toId } from "@/shared/lib/utils"
import { formatTaxLabel } from "@/shared/utils/formatters"
import { import {
expenseFormSchema, expenseFormSchema,
@ -89,7 +90,7 @@ function mapToFormValues(data: unknown): ExpenseFormValues {
category: toRelation(d.category_id, d.category?.name ?? d.category?.title ?? d.category_name), category: toRelation(d.category_id, d.category?.name ?? d.category?.title ?? d.category_name),
vendor: toRelation(d.vendor_id, d.vendor?.company_name ?? d.vendor?.name ?? d.vendor_name), vendor: toRelation(d.vendor_id, d.vendor?.company_name ?? d.vendor?.name ?? d.vendor_name),
department: toRelation(d.department_id, d.department?.name ?? d.department_name), department: toRelation(d.department_id, d.department?.name ?? d.department_name),
tax: toRelation(d.tax_id, d.tax?.title ? `${d.tax.title} (${d.tax.rate}%)` : undefined), tax: toRelation(d.tax_id, formatTaxLabel(d.tax, d.tax_title ?? "") || undefined),
title: d.title || "", title: d.title || "",
invoice_number: d.invoice_number || "", invoice_number: d.invoice_number || "",
expense_date: d.expense_date ? d.expense_date.split("T")[0] : "", expense_date: d.expense_date ? d.expense_date.split("T")[0] : "",

View File

@ -20,8 +20,7 @@ import {
} from "@/shared/components/ui/card" } from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge" import { Badge } from "@/shared/components/ui/badge"
import { cn } from "@/shared/lib/utils" import { cn } from "@/shared/lib/utils"
import { formatDate, formatCurrency, formatEnum } from "@/shared/utils/formatters" import { formatDate, formatCurrency, formatEnum, formatTaxLabel } from "@/shared/utils/formatters"
import { getFullName } from "@/shared/utils/getFullName"
import { useExpense } from "./expense-context" import { useExpense } from "./expense-context"
function InfoItem({ function InfoItem({
@ -68,9 +67,7 @@ export function ExpenseGeneralInfo() {
const labels = (expense as any)?.labels || [] const labels = (expense as any)?.labels || []
const balanceDue = expense.balance_due ?? null const balanceDue = expense.balance_due ?? null
const paymentsM = expense.payments_made ?? null const paymentsM = expense.payments_made ?? null
const taxLabel = expense.tax?.title const taxLabel = formatTaxLabel(expense.tax, "Tax")
? `${expense.tax.title} (${expense.tax.rate}%)`
: "Tax"
return ( return (
<div className="grid gap-6"> <div className="grid gap-6">
@ -122,7 +119,7 @@ export function ExpenseGeneralInfo() {
{/* ── Vendor ── */} {/* ── Vendor ── */}
<Card className="flex flex-col gap-1 p-4"> <Card className="flex flex-col gap-1 p-4">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Vendor</span> <span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Vendor</span>
<span className="mt-1 text-lg font-semibold">{vendor.company_name || getFullName(vendor as any) || "—"}</span> <span className="mt-1 text-lg font-semibold">{vendor.name || "—"}</span>
</Card> </Card>
{/* ── Expense Details ── */} {/* ── Expense Details ── */}

View File

@ -21,6 +21,7 @@ import { useAuthApi } from "@/shared/useApi"
import { useResourceForm } from "@/shared/hooks/use-resource-form" import { useResourceForm } from "@/shared/hooks/use-resource-form"
import { useFormMutation } from "@/shared/hooks/use-form-mutation" import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { toRelation, toId } from "@/shared/lib/utils" import { toRelation, toId } from "@/shared/lib/utils"
import { formatTaxLabel } from "@/shared/utils/formatters"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { import {
@ -120,7 +121,7 @@ function mapToFormValues(data: unknown): InvoiceFormValues {
has_insurance: d.has_insurance ?? false, has_insurance: d.has_insurance ?? false,
discount: d.discount || "no", discount: d.discount || "no",
discount_amount: d.discount_amount != null ? Number(d.discount_amount) : undefined, discount_amount: d.discount_amount != null ? Number(d.discount_amount) : undefined,
tax: toRelation(d.tax_id, d.tax_title ?? d.tax?.title), tax: toRelation(d.tax_id, d.tax_title ?? (formatTaxLabel(d.tax, "") || undefined)),
deposit_to: d.deposit_to || "", deposit_to: d.deposit_to || "",
notes: d.notes || "", notes: d.notes || "",
terms_and_conditions: d.terms_and_conditions || "", terms_and_conditions: d.terms_and_conditions || "",

View File

@ -36,7 +36,7 @@ export function InvoiceSequenceForm({ resourceId, initialData, onSuccess }: Invo
const isEditing = !!resourceId const isEditing = !!resourceId
const form = useForm<InvoiceSequenceFormValues>({ const form = useForm<InvoiceSequenceFormValues>({
resolver: zodResolver(invoiceSequenceSchema), resolver: zodResolver(invoiceSequenceSchema) as any,
defaultValues: { defaultValues: {
title: "", title: "",
sequence_title: "", sequence_title: "",

View File

@ -16,6 +16,7 @@ import {
import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form" import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form"
import { toast } from "sonner" import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi" import { useAuthApi } from "@/shared/useApi"
import { formatTaxLabel } from "@/shared/utils/formatters"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api" import { DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api"
@ -71,7 +72,7 @@ function mapToFormValues(data: unknown): JobCardExpenseItemFormValues {
? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) } ? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) }
: null, : null,
tax: d.tax_id != null tax: d.tax_id != null
? { value: String(d.tax_id), label: d.tax ? `${d.tax.title} (${d.tax.rate}%)` : String(d.tax_id) } ? { value: String(d.tax_id), label: formatTaxLabel(d.tax, String(d.tax_id)) }
: null, : null,
quantity: d.quantity ?? 1, quantity: d.quantity ?? 1,
rate: d.rate != null ? Number(d.rate) : 0, rate: d.rate != null ? Number(d.rate) : 0,

View File

@ -20,6 +20,7 @@ import { useAuthApi } from "@/shared/useApi"
import { useResourceForm } from "@/shared/hooks/use-resource-form" import { useResourceForm } from "@/shared/hooks/use-resource-form"
import { useFormMutation } from "@/shared/hooks/use-form-mutation" import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { toRelation, toId } from "@/shared/lib/utils" import { toRelation, toId } from "@/shared/lib/utils"
import { formatTaxLabel } from "@/shared/utils/formatters"
import { import {
jobCardFormSchema, jobCardFormSchema,
@ -106,7 +107,7 @@ function mapToFormValues(data: unknown): JobCardFormValues {
sales_person: toRelation(d.sales_person_id, d.sales_person ? `${d.sales_person.first_name} ${d.sales_person.last_name}` : undefined), sales_person: toRelation(d.sales_person_id, d.sales_person ? `${d.sales_person.first_name} ${d.sales_person.last_name}` : undefined),
insurance_type: toRelation(d.insurance_type_id, d.insurance_type?.name), insurance_type: toRelation(d.insurance_type_id, d.insurance_type?.name),
insurer: toRelation(d.insurer_id, d.insurer ? `${d.insurer.first_name} ${d.insurer.last_name}`.trim() || d.insurer.company_name : undefined), insurer: toRelation(d.insurer_id, d.insurer ? `${d.insurer.first_name} ${d.insurer.last_name}`.trim() || d.insurer.company_name : undefined),
tax: toRelation(d.tax_id, d.tax ? `${d.tax.title} (${d.tax.rate}%)` : undefined), tax: toRelation(d.tax_id, formatTaxLabel(d.tax, "") || undefined),
order_number: d.order_number || "", order_number: d.order_number || "",
estimate_number: d.estimate_number || "", estimate_number: d.estimate_number || "",
status: d.status || "draft", status: d.status || "draft",

View File

@ -16,6 +16,7 @@ import {
import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form" import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form"
import { toast } from "sonner" import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi" import { useAuthApi } from "@/shared/useApi"
import { formatTaxLabel } from "@/shared/utils/formatters"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api" import { DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api"
@ -71,7 +72,7 @@ function mapToFormValues(data: unknown): JobCardPartFormValues {
? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) } ? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) }
: null, : null,
tax: d.tax_id != null tax: d.tax_id != null
? { value: String(d.tax_id), label: d.tax ? `${d.tax.title} (${d.tax.rate}%)` : String(d.tax_id) } ? { value: String(d.tax_id), label: formatTaxLabel(d.tax, String(d.tax_id)) }
: null, : null,
quantity: d.quantity ?? 1, quantity: d.quantity ?? 1,
rate: d.rate != null ? Number(d.rate) : 0, rate: d.rate != null ? Number(d.rate) : 0,

View File

@ -17,6 +17,7 @@ import {
import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form" import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form"
import { toast } from "sonner" import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi" import { useAuthApi } from "@/shared/useApi"
import { formatTaxLabel } from "@/shared/utils/formatters"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { RateType, DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api" import { RateType, DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api"
@ -80,7 +81,7 @@ function mapToFormValues(data: unknown): JobCardServiceFormValues {
? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) } ? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) }
: undefined as any, : undefined as any,
tax: d.tax_id != null tax: d.tax_id != null
? { value: String(d.tax_id), label: d.tax ? `${d.tax.title} (${d.tax.rate}%)` : String(d.tax_id) } ? { value: String(d.tax_id), label: formatTaxLabel(d.tax, String(d.tax_id)) }
: null, : null,
rate_type: d.rate_type ?? "flat_rate", rate_type: d.rate_type ?? "flat_rate",
labor_rate: d.labor_rate labor_rate: d.labor_rate

View File

@ -56,6 +56,25 @@ export function formatEnum(value?: string | null): string {
.join(" ") .join(" ")
} }
type TaxLabelValue = {
name?: string | null
title?: string | null
rate?: string | number | null
}
export function formatTaxLabel(
value?: TaxLabelValue | null,
emptyFallback = "—",
): string {
const label = value?.name ?? value?.title
const rate = value?.rate
if (!label) return emptyFallback
if (rate == null || rate === "") return label
return `${label} (${rate}%)`
}
/** /**
* Format a number with locale-aware thousand separators. * Format a number with locale-aware thousand separators.
* e.g. 150000 "150,000" * e.g. 150000 "150,000"

View File

@ -1,4 +1,9 @@
export const getFullName = <T extends { first_name?: string | undefined; last_name?: string | undefined }>(user?: T) => { type NameLike = {
first_name?: string | null
last_name?: string | null
}
export const getFullName = <T extends NameLike>(user?: T | null) => {
const firstName = user?.first_name ?? "" const firstName = user?.first_name ?? ""
const lastName = user?.last_name ?? "" const lastName = user?.last_name ?? ""
return `${firstName} ${lastName}`.trim() return `${firstName} ${lastName}`.trim()