This commit is contained in:
2026-04-09 15:17:07 +03:00
parent c5f6d2f596
commit 29cc9c6f4e
23 changed files with 420 additions and 190 deletions
@@ -1,16 +1,14 @@
import { z } from "zod"
import { AppointmentStatus } from "@garage/api"
const relationFieldSchema = z
.object({ value: z.string(), label: z.string() })
.nullable()
const APPOINTMENT_STATUS_OPTIONS = [
{ value: "requested", label: "Requested" },
{ value: "confirmed", label: "Confirmed" },
{ value: "in_progress", label: "In Progress" },
{ value: "completed", label: "Completed" },
{ value: "cancelled", label: "Cancelled" },
]
const APPOINTMENT_STATUS_OPTIONS = AppointmentStatus.map((v) => ({
value: v,
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}))
const appointmentFormSchema = z.object({
title: z.string().min(1, "Title is required"),
@@ -22,7 +22,7 @@ import {
creditNoteFormSchema,
type CreditNoteFormValues,
} from "./credit-note.schema"
import { CREDIT_NOTE_ROUTES, DEPARTMENT_ROUTES } from "@garage/api"
import { CREDIT_NOTE_ROUTES, DEPARTMENT_ROUTES, CreditNoteStatus } from "@garage/api"
import { RhfCustomerSelectField } from "@/modules/customers/rhf-customer-select-field"
// ── Shared mapOption for async selects ──
@@ -36,12 +36,10 @@ const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) =
// ── Constants ──
const STATUS_OPTIONS = [
{ value: "draft", label: "Draft" },
{ value: "open", label: "Open" },
{ value: "applied", label: "Applied" },
{ value: "void", label: "Void" },
]
const STATUS_OPTIONS = CreditNoteStatus.map((v) => ({
value: v,
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}))
// ── Props ──
@@ -22,7 +22,7 @@ import {
employeeFormSchema,
type EmployeeFormValues,
} from "./employee.schema"
import { EMPLOYEE_ROUTES, DEPARTMENT_ROUTES, SHOP_TIMING_ROUTES, SHOP_CALENDAR_ROUTES } from "@garage/api"
import { EMPLOYEE_ROUTES, DEPARTMENT_ROUTES, SHOP_TIMING_ROUTES, SHOP_CALENDAR_ROUTES, EmployeeType } from "@garage/api"
// ── Constants ──
@@ -31,10 +31,10 @@ const STATUS_OPTIONS = [
{ value: "inactive", label: "Inactive" },
]
const TYPE_OPTIONS = [
{ value: "employee", label: "Employee" },
{ value: "contractor", label: "Contractor" },
]
const TYPE_OPTIONS = EmployeeType.map((v) => ({
value: v,
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}))
// ── Props ──
@@ -1,11 +1,12 @@
import { z } from "zod"
import { EmployeeType } from "@garage/api"
const relationFieldSchema = z
.object({ value: z.string(), label: z.string() })
.nullable()
const STATUS_OPTIONS = ["active", "inactive"] as const
const TYPE_OPTIONS = ["employee", "contractor"] as const
const TYPE_OPTIONS = EmployeeType
const employeeFormSchema = z.object({
department: relationFieldSchema,
@@ -22,15 +22,15 @@ import {
expenseFormSchema,
type ExpenseFormValues,
} from "./expense.schema"
import { EXPENSE_ROUTES, JOB_CARD_ROUTES, VENDOR_ROUTES, DEPARTMENT_ROUTES } from "@garage/api"
import { EXPENSE_ROUTES, JOB_CARD_ROUTES, VENDOR_ROUTES, DEPARTMENT_ROUTES, ExpenseStatus } from "@garage/api"
import { getFullName } from "@/shared/utils/getFullName"
// ── Constants ──
const STATUS_OPTIONS = [
{ value: "open", label: "Open" },
{ value: "paid", label: "Paid" },
]
const STATUS_OPTIONS = ExpenseStatus.map((v) => ({
value: v,
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}))
// ── Props ──
@@ -11,6 +11,7 @@ import {
RhfSelectField,
RhfTextareaField,
RhfAsyncSelectField,
RhfAutoGenerateField,
} from "@/shared/components/form"
import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi"
@@ -22,19 +23,16 @@ import {
invoiceFormSchema,
type InvoiceFormValues,
} from "./invoice.schema"
import { INVOICE_ROUTES, DEPARTMENT_ROUTES } from "@garage/api"
import { INVOICE_ROUTES, DEPARTMENT_ROUTES, InvoiceStatus } from "@garage/api"
import { RhfCustomerSelectField } from "@/modules/customers/rhf-customer-select-field"
import { RhfVehicleSelectField } from "@/modules/vehicles/rhf-vehicle-select-field"
// ── Constants ──
const STATUS_OPTIONS = [
{ value: "draft", label: "Draft" },
{ value: "open", label: "Open" },
{ value: "paid", label: "Paid" },
{ value: "overdue", label: "Overdue" },
{ value: "void", label: "Void" },
]
const STATUS_OPTIONS = InvoiceStatus.map((v) => ({
value: v,
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}))
// ── Props ──
@@ -148,7 +146,7 @@ export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormP
<RhfTextField name="subject" label="Subject" placeholder="Invoice subject" required />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField name="invoice_number" label="Invoice Number" placeholder="INV-0001" />
<RhfAutoGenerateField table="invoices" name="invoice_number" label="Invoice Number" placeholder="INV-0001" />
<RhfSelectField
name="status"
label="Status"
+62 -62
View File
@@ -22,13 +22,13 @@ import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { toId } from "@/shared/lib/utils"
import { partFormSchema, type PartFormValues } from "./part.schema"
import { PARTS_ROUTES } from "@garage/api"
import { CrudListItem, PARTS_ROUTES, PartsClient } from "@garage/api"
// ── Props ──
export type PartFormProps = {
resourceId?: string | null
initialData?: unknown
initialData?: CrudListItem<PartsClient> | null
onSuccess?: () => void
}
@@ -150,59 +150,59 @@ export function PartForm({ resourceId, initialData, onSuccess }: PartFormProps)
/>
</div>
{!isEditing && (
<>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfAsyncSelectField
name="shop_type"
label="Shop Type"
placeholder="Select shop type"
queryKey={["shop-types"]}
listFn={() => api.shopTypes.list()}
mapOption={mapLookupOption}
createForm={(props) => <ShopTypeInlineForm {...props} />}
createLabel="Shop Type"
{...STORE_OBJECT}
/>
<RhfAsyncSelectField
name="category"
label="Category"
placeholder="Select category"
queryKey={["inventory-categories"]}
listFn={() => api.inventory.listCategories()}
mapOption={mapLookupOption}
createForm={(props) => <InventoryCategoryInlineForm {...props} />}
createLabel="Category"
{...STORE_OBJECT}
/>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfAsyncSelectField
name="unit_type"
label="Unit Type"
placeholder="Select unit type"
queryKey={["unit-types"]}
listFn={() => api.inventory.listUnitTypes()}
mapOption={mapLookupOption}
createForm={(props) => <UnitTypeInlineForm {...props} />}
createLabel="Unit Type"
{...STORE_OBJECT}
/>
<RhfAsyncSelectField
name="department"
label="Department"
placeholder="Select department"
queryKey={["departments"]}
listFn={() => api.departments.list()}
mapOption={(item: any) => ({ value: String(item.id), label: item.name ?? String(item.id) })}
createForm={(props) => <DepartmentInlineForm {...props} />}
createLabel="Department"
{...STORE_OBJECT}
/>
</div>
</>
)}
<>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfAsyncSelectField
name="shop_type"
label="Shop Type"
placeholder="Select shop type"
queryKey={["shop-types"]}
listFn={() => api.shopTypes.list()}
mapOption={mapLookupOption}
createForm={(props) => <ShopTypeInlineForm {...props} />}
createLabel="Shop Type"
{...STORE_OBJECT}
/>
<RhfAsyncSelectField
name="category"
label="Category"
placeholder="Select category"
queryKey={["inventory-categories"]}
listFn={() => api.inventory.listCategories()}
mapOption={mapLookupOption}
createForm={(props) => <InventoryCategoryInlineForm {...props} />}
createLabel="Category"
{...STORE_OBJECT}
/>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfAsyncSelectField
name="unit_type"
label="Unit Type"
placeholder="Select unit type"
queryKey={["unit-types"]}
listFn={() => api.inventory.listUnitTypes()}
mapOption={mapLookupOption}
createForm={(props) => <UnitTypeInlineForm {...props} />}
createLabel="Unit Type"
{...STORE_OBJECT}
/>
<RhfAsyncSelectField
name="department"
label="Department"
placeholder="Select department"
queryKey={["departments"]}
listFn={() => api.departments.list()}
mapOption={(item: any) => ({ value: String(item.id), label: item.name ?? String(item.id) })}
createForm={(props) => <DepartmentInlineForm {...props} />}
createLabel="Department"
{...STORE_OBJECT}
/>
</div>
</>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField
@@ -211,14 +211,14 @@ export function PartForm({ resourceId, initialData, onSuccess }: PartFormProps)
placeholder="0.00"
type="number"
/>
{!isEditing && (
<RhfTextField
name="purchase_price"
label="Purchase Price"
placeholder="0.00"
type="number"
/>
)}
<RhfTextField
name="purchase_price"
label="Purchase Price"
placeholder="0.00"
type="number"
/>
</div>
<RhfTextareaField
@@ -1,7 +1,8 @@
export const DEPARTMENT_ASSIGNMENT_TYPE_OPTIONS = [
{ value: "none", label: "None" },
{ value: "bays", label: "Bays" },
{ value: "outsourced", label: "Outsourced" },
] as const
import { AssignmentType } from "@garage/api"
export type DepartmentAssignmentType = typeof DEPARTMENT_ASSIGNMENT_TYPE_OPTIONS[number]["value"]
export const DEPARTMENT_ASSIGNMENT_TYPE_OPTIONS = AssignmentType.map((v) => ({
value: v,
label: v.charAt(0).toUpperCase() + v.slice(1),
}))
export type DepartmentAssignmentType = (typeof AssignmentType)[number]