updates
This commit is contained in:
@@ -8,10 +8,13 @@ import { FieldGroup } from "@/shared/components/ui/field"
|
||||
import {
|
||||
Rhform,
|
||||
RhfTextField,
|
||||
RhfTextareaField,
|
||||
RhfSelectField,
|
||||
RhfAsyncSelectField,
|
||||
RhfCheckboxField,
|
||||
} from "@/shared/components/form"
|
||||
import { UnitTypeInlineForm } from "@/modules/services/inline-forms/unit-type-inline-form"
|
||||
import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form"
|
||||
import { toast } from "sonner"
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import { useResourceForm } from "@/shared/hooks/use-resource-form"
|
||||
@@ -19,7 +22,13 @@ import { useFormMutation } from "@/shared/hooks/use-form-mutation"
|
||||
import { toRelation, toId } from "@/shared/lib/utils"
|
||||
|
||||
import { expenseItemFormSchema, type ExpenseItemFormValues } from "./expense-item.schema"
|
||||
import { EXPENSE_ITEM_ROUTES, INVENTORY_CATEGORY_ROUTES } from "@garage/api"
|
||||
import {
|
||||
EXPENSE_ITEM_ROUTES,
|
||||
INVENTORY_CATEGORY_ROUTES,
|
||||
INVENTORY_ROUTES,
|
||||
DEPARTMENT_ROUTES,
|
||||
VENDOR_ROUTES,
|
||||
} from "@garage/api"
|
||||
import { InventoryCategoryCrudDialog } from "./inventory-category-crud-dialog"
|
||||
|
||||
// ── Constants ──
|
||||
@@ -48,10 +57,19 @@ export type ExpenseItemFormProps = {
|
||||
const DEFAULT_VALUES: ExpenseItemFormValues = {
|
||||
item_type: "Expense",
|
||||
item_name: "",
|
||||
sku: "",
|
||||
item_code: "",
|
||||
description: "",
|
||||
category: null,
|
||||
unit_type: null,
|
||||
department: null,
|
||||
purchase_information: true,
|
||||
purchase_price: undefined,
|
||||
purchase_chart_of_account: "",
|
||||
purchase_information: true,
|
||||
purchase_preferred_vendor: null,
|
||||
sales_information: false,
|
||||
selling_price: undefined,
|
||||
sales_chart_of_account: "",
|
||||
is_active: true,
|
||||
}
|
||||
|
||||
@@ -63,10 +81,22 @@ function mapToFormValues(data: unknown): ExpenseItemFormValues {
|
||||
return {
|
||||
item_type: d.item_type || "Expense",
|
||||
item_name: d.item_name || "",
|
||||
sku: d.sku || "",
|
||||
item_code: d.item_code || "",
|
||||
description: d.description || "",
|
||||
category: toRelation(d.category_id, d.category_title ?? d.category_name),
|
||||
unit_type: toRelation(d.unit_type_id, d.unit_type_title ?? d.unit_type_name),
|
||||
department: toRelation(d.department_id, d.department_name ?? d.department_title),
|
||||
purchase_information: d.purchase_information ?? true,
|
||||
purchase_price: d.purchase_price ?? undefined,
|
||||
purchase_chart_of_account: d.purchase_chart_of_account || "",
|
||||
purchase_information: d.purchase_information ?? true,
|
||||
purchase_preferred_vendor: toRelation(
|
||||
d.purchase_preferred_vendor_id,
|
||||
d.purchase_preferred_vendor_name,
|
||||
),
|
||||
sales_information: d.sales_information ?? false,
|
||||
selling_price: d.selling_price ?? undefined,
|
||||
sales_chart_of_account: d.sales_chart_of_account || "",
|
||||
is_active: d.is_active ?? true,
|
||||
}
|
||||
}
|
||||
@@ -75,10 +105,19 @@ function mapFormToPayload(values: ExpenseItemFormValues) {
|
||||
return {
|
||||
item_type: values.item_type,
|
||||
item_name: values.item_name,
|
||||
sku: values.sku || undefined,
|
||||
item_code: values.item_code || undefined,
|
||||
description: values.description || undefined,
|
||||
category_id: toId(values.category),
|
||||
unit_type_id: toId(values.unit_type),
|
||||
department_id: toId(values.department),
|
||||
purchase_information: values.purchase_information,
|
||||
purchase_price: values.purchase_price,
|
||||
purchase_chart_of_account: values.purchase_chart_of_account || undefined,
|
||||
purchase_information: values.purchase_information,
|
||||
purchase_preferred_vendor_id: toId(values.purchase_preferred_vendor),
|
||||
sales_information: values.sales_information,
|
||||
selling_price: values.selling_price,
|
||||
sales_chart_of_account: values.sales_chart_of_account || undefined,
|
||||
is_active: values.is_active,
|
||||
}
|
||||
}
|
||||
@@ -129,6 +168,7 @@ export function ExpenseItemForm({ resourceId, initialData, onSuccess }: ExpenseI
|
||||
)}
|
||||
|
||||
<FieldGroup>
|
||||
{/* Basic Info */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfSelectField
|
||||
name="item_type"
|
||||
@@ -144,6 +184,27 @@ export function ExpenseItemForm({ resourceId, initialData, onSuccess }: ExpenseI
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfTextField
|
||||
name="sku"
|
||||
label="SKU"
|
||||
placeholder="e.g. EXP-001"
|
||||
/>
|
||||
<RhfTextField
|
||||
name="item_code"
|
||||
label="Item Code"
|
||||
placeholder="e.g. EXP-001"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RhfTextareaField
|
||||
name="description"
|
||||
label="Description"
|
||||
placeholder="Optional description"
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
{/* Classification */}
|
||||
<div>
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<span className="text-sm font-medium">Category</span>
|
||||
@@ -160,6 +221,37 @@ export function ExpenseItemForm({ resourceId, initialData, onSuccess }: ExpenseI
|
||||
/>
|
||||
</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={[INVENTORY_ROUTES.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={[DEPARTMENT_ROUTES.INDEX]}
|
||||
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>
|
||||
|
||||
{/* Purchase Information */}
|
||||
{/* <RhfCheckboxField
|
||||
name="purchase_information"
|
||||
label="Purchase Information"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfTextField
|
||||
name="purchase_price"
|
||||
@@ -174,16 +266,41 @@ export function ExpenseItemForm({ resourceId, initialData, onSuccess }: ExpenseI
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<RhfCheckboxField
|
||||
name="purchase_information"
|
||||
label="Purchase Information"
|
||||
<RhfAsyncSelectField
|
||||
name="purchase_preferred_vendor"
|
||||
label="Preferred Vendor"
|
||||
placeholder="Select vendor"
|
||||
queryKey={[VENDOR_ROUTES.INDEX]}
|
||||
listFn={() => api.vendors.list()}
|
||||
mapOption={(item: any) => ({ value: String(item.id), label: item.name ?? String(item.id) })}
|
||||
{...STORE_OBJECT}
|
||||
/> */}
|
||||
|
||||
{/* Sales Information */}
|
||||
{/* <RhfCheckboxField
|
||||
name="sales_information"
|
||||
label="Sales Information"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfTextField
|
||||
name="selling_price"
|
||||
label="Selling Price"
|
||||
placeholder="0.00"
|
||||
type="number"
|
||||
/>
|
||||
<RhfCheckboxField
|
||||
name="is_active"
|
||||
label="Active"
|
||||
<RhfTextField
|
||||
name="sales_chart_of_account"
|
||||
label="Sales Chart of Account"
|
||||
placeholder="e.g. Revenue"
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Status */}
|
||||
<RhfCheckboxField
|
||||
name="is_active"
|
||||
label="Active"
|
||||
/>
|
||||
</FieldGroup>
|
||||
|
||||
<div className="mt-6 flex justify-end">
|
||||
|
||||
@@ -7,10 +7,22 @@ export const relationFieldSchema = z
|
||||
export const expenseItemFormSchema = z.object({
|
||||
item_type: z.string().min(1, "Item type is required"),
|
||||
item_name: z.string().min(1, "Item name is required"),
|
||||
sku: z.string().optional(),
|
||||
item_code: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
category: relationFieldSchema,
|
||||
unit_type: relationFieldSchema,
|
||||
department: relationFieldSchema,
|
||||
// Purchase
|
||||
purchase_information: z.boolean().default(true),
|
||||
purchase_price: z.coerce.number().min(0).optional(),
|
||||
purchase_chart_of_account: z.string().optional(),
|
||||
purchase_information: z.boolean().default(true),
|
||||
// purchase_preferred_vendor: relationFieldSchema,
|
||||
// Sales
|
||||
sales_information: z.boolean().default(false),
|
||||
selling_price: z.coerce.number().min(0).optional(),
|
||||
sales_chart_of_account: z.string().optional(),
|
||||
// Status
|
||||
is_active: z.boolean().default(true),
|
||||
})
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ type ExpenseLineItem = {
|
||||
title: string
|
||||
quantity: number
|
||||
rate: number
|
||||
chart_of_account?: string
|
||||
discount_amount?: number
|
||||
description?: string
|
||||
}
|
||||
|
||||
@@ -34,6 +36,8 @@ export type ExpenseItemsSelectorFieldProps<
|
||||
name: TName & (TValues[TName] extends ExpenseItemsFieldConstraint ? TName : never)
|
||||
label?: string
|
||||
triggerLabel?: string
|
||||
showChartOfAccount?: boolean
|
||||
showDiscount?: boolean
|
||||
}
|
||||
|
||||
export function ExpenseItemsSelectorField<
|
||||
@@ -43,6 +47,8 @@ export function ExpenseItemsSelectorField<
|
||||
name,
|
||||
label = "Expense Items",
|
||||
triggerLabel = "Add Expense Items",
|
||||
showChartOfAccount = false,
|
||||
showDiscount = false,
|
||||
}: ExpenseItemsSelectorFieldProps<TValues, TName>) {
|
||||
return (
|
||||
<RhfResourceField<TValues, TName, ExpenseItemsClient>
|
||||
@@ -69,6 +75,7 @@ export function ExpenseItemsSelectorField<
|
||||
title: r.item_name || "",
|
||||
quantity: 1,
|
||||
rate: Number(r.purchase_price) || 0,
|
||||
chart_of_account: r.purchase_chart_of_account ? String(r.purchase_chart_of_account) : "",
|
||||
description: "",
|
||||
} as any
|
||||
}}
|
||||
@@ -79,6 +86,8 @@ export function ExpenseItemsSelectorField<
|
||||
<TableHead>Expense Item</TableHead>
|
||||
<TableHead className="w-24">Qty</TableHead>
|
||||
<TableHead className="w-28">Rate</TableHead>
|
||||
{showChartOfAccount && <TableHead className="w-36">Chart of Account</TableHead>}
|
||||
{showDiscount && <TableHead className="w-28">Discount</TableHead>}
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead className="w-12" />
|
||||
</TableRow>
|
||||
@@ -110,6 +119,32 @@ export function ExpenseItemsSelectorField<
|
||||
className="h-8 w-24"
|
||||
/>
|
||||
</TableCell>
|
||||
{showChartOfAccount && (
|
||||
<TableCell>
|
||||
<Input
|
||||
value={item.chart_of_account ?? ""}
|
||||
onChange={(e) =>
|
||||
update(index, { ...item, chart_of_account: e.target.value } as any)
|
||||
}
|
||||
placeholder="Optional"
|
||||
className="h-8 w-32"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{showDiscount && (
|
||||
<TableCell>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
step={0.01}
|
||||
value={item.discount_amount ?? 0}
|
||||
onChange={(e) =>
|
||||
update(index, { ...item, discount_amount: Number(e.target.value) || 0 } as any)
|
||||
}
|
||||
className="h-8 w-24"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<Input
|
||||
value={item.description ?? ""}
|
||||
|
||||
Reference in New Issue
Block a user