fix(dashboard): resolve Asma tester-reported UI issues
- 3.1 currency: replace hardcoded $ with AED <Money/> in item & job-card price cells - 5.1 calendar: /calendars now renders the month calendar (was redirect to list) - 7.2 nav: remove dead Time Clocks / Time Sheets links (no routes, 404) - 5.4 check-in: enforce required department at schema level - 5.6 payments received: add invoice picker so the invoice is marked Paid - 5.2 share link: forward ws workspace param from the public inspection page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0171daea38
commit
11d920ba6a
@@ -1,4 +1,5 @@
|
||||
import { ColumnHeader } from "@/shared/data-view/table-view"
|
||||
import { Money } from "@/shared/components/money"
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
/** Core expense-item columns shared between the expense items page and selector dialogs. */
|
||||
@@ -16,7 +17,7 @@ export const expenseItemColumns = {
|
||||
header: ({ column }) => <ColumnHeader column={column} title="Purchase Price" />,
|
||||
cell: ({ row }) => {
|
||||
const val = (row.original as any).purchase_price
|
||||
return val != null ? `$${Number(val).toFixed(2)}` : "—"
|
||||
return val != null ? <Money value={val} /> : "—"
|
||||
},
|
||||
},
|
||||
chartOfAccount: {
|
||||
|
||||
@@ -38,6 +38,16 @@ const checkInSchema = z.object({
|
||||
department: z.object({ value: z.any(), label: z.string() }).nullable().optional(),
|
||||
delivery_date: z.string().optional(),
|
||||
delivery_time: z.string().optional(),
|
||||
}).superRefine((values, ctx) => {
|
||||
// Department is marked required in the UI (asterisk); enforce it at the
|
||||
// schema level so the form cannot be submitted without one.
|
||||
if (!values.department) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["department"],
|
||||
message: "Department is required",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
type CheckInFormValues = z.infer<typeof checkInSchema>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ColumnHeader } from "@/shared/data-view/table-view"
|
||||
import { Money } from "@/shared/components/money"
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
@@ -32,7 +33,7 @@ export const partColumns = {
|
||||
header: ({ column }) => <ColumnHeader column={column} title="Sell Price" />,
|
||||
cell: ({ row }) => {
|
||||
const val = (row.original as any).selling_price
|
||||
return val != null ? `$${Number(val).toFixed(2)}` : "—"
|
||||
return val != null ? <Money value={val} /> : "—"
|
||||
},
|
||||
},
|
||||
purchasePrice: {
|
||||
@@ -40,7 +41,7 @@ export const partColumns = {
|
||||
header: ({ column }) => <ColumnHeader column={column} title="Purchase Price" />,
|
||||
cell: ({ row }) => {
|
||||
const val = (row.original as any).purchase_price
|
||||
return val != null ? `$${Number(val).toFixed(2)}` : "—"
|
||||
return val != null ? <Money value={val} /> : "—"
|
||||
},
|
||||
},
|
||||
stock: {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
paymentReceivedFormSchema,
|
||||
type PaymentReceivedFormValues,
|
||||
} from "./payment-received.schema"
|
||||
import { PAYMENT_MODE_ROUTES, CUSTOMER_ROUTES, JOB_CARD_ROUTES, PAYMENT_RECEIVED_ROUTES } from "@garage/api"
|
||||
import { PAYMENT_MODE_ROUTES, CUSTOMER_ROUTES, JOB_CARD_ROUTES, INVOICE_ROUTES, PAYMENT_RECEIVED_ROUTES } from "@garage/api"
|
||||
|
||||
// ── Props ──
|
||||
|
||||
@@ -45,6 +45,7 @@ export type PaymentReceivedFormProps = {
|
||||
|
||||
const DEFAULT_VALUES: PaymentReceivedFormValues = {
|
||||
job_card: null,
|
||||
invoice: null,
|
||||
payment_mode: null,
|
||||
customer: null,
|
||||
amount_received: 0,
|
||||
@@ -61,6 +62,9 @@ function mapToFormValues(data: unknown): PaymentReceivedFormValues {
|
||||
const jobCardId = d.job_card_id ?? d.job_card?.id
|
||||
const jobCardLabel = d.job_card?.title ?? d.job_card_name
|
||||
|
||||
const invoiceId = d.invoice_id ?? d.invoice?.id
|
||||
const invoiceLabel = d.invoice?.invoice_number ?? d.invoice?.title ?? d.invoice_number
|
||||
|
||||
const paymentModeId = d.payment_mode_id ?? d.payment_mode?.id
|
||||
const paymentModeLabel = d.payment_mode?.title ?? d.payment_mode?.name ?? d.payment_mode_name
|
||||
|
||||
@@ -76,6 +80,7 @@ function mapToFormValues(data: unknown): PaymentReceivedFormValues {
|
||||
|
||||
return {
|
||||
job_card: toRelation(jobCardId, jobCardLabel),
|
||||
invoice: toRelation(invoiceId, invoiceLabel),
|
||||
payment_mode: toRelation(paymentModeId, paymentModeLabel),
|
||||
customer: toRelation(customerId, customerLabel),
|
||||
amount_received: d.amount_received != null ? Number(d.amount_received) : 0,
|
||||
@@ -86,6 +91,9 @@ function mapToFormValues(data: unknown): PaymentReceivedFormValues {
|
||||
}
|
||||
|
||||
function mapFormToPayload(values: PaymentReceivedFormValues, invoiceId?: string | null) {
|
||||
// Prefer the invoice the form was opened from; otherwise the one picked in
|
||||
// the standalone form. Linking invoice_id is what flips the invoice to Paid.
|
||||
const resolvedInvoiceId = invoiceId ?? toId(values.invoice)
|
||||
return {
|
||||
job_card_id: toId(values.job_card),
|
||||
payment_mode_id: toId(values.payment_mode),
|
||||
@@ -94,7 +102,7 @@ function mapFormToPayload(values: PaymentReceivedFormValues, invoiceId?: string
|
||||
payment_number: values.payment_number || undefined,
|
||||
payment_date: values.payment_date,
|
||||
note: values.note || undefined,
|
||||
...(invoiceId ? { invoice_id: Number(invoiceId) } : {}),
|
||||
...(resolvedInvoiceId ? { invoice_id: Number(resolvedInvoiceId) } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +122,10 @@ export function PaymentReceivedForm({ resourceId, initialData, onSuccess, defaul
|
||||
|
||||
const isJobCardLocked = !resourceId && (lockJobCard ?? !!defaultJobCard?.id)
|
||||
const isCustomerLocked = !resourceId && (lockCustomer ?? !!invoiceCustomer?.id)
|
||||
// When opened from a specific invoice the link is implicit (invoiceId prop).
|
||||
// Standalone (Payments Received → Add) needs a picker so the payment can be
|
||||
// tied to an invoice — otherwise the invoice never shows as Paid.
|
||||
const showInvoiceField = !invoiceId
|
||||
|
||||
const customerLabel = invoiceCustomer?.first_name
|
||||
? `${invoiceCustomer.first_name} ${invoiceCustomer.last_name || ""}`.trim()
|
||||
@@ -225,6 +237,22 @@ export function PaymentReceivedForm({ resourceId, initialData, onSuccess, defaul
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showInvoiceField && (
|
||||
<RhfAsyncSelectField
|
||||
name="invoice"
|
||||
label="Invoice"
|
||||
placeholder="Select invoice to mark as paid"
|
||||
description="Link this payment to an invoice so it is marked paid."
|
||||
queryKey={[INVOICE_ROUTES.INDEX]}
|
||||
listFn={() => api.invoices.list()}
|
||||
mapOption={(item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.invoice_number ?? item.title ?? `#${item.id}`,
|
||||
})}
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfTextField
|
||||
name="amount_received"
|
||||
|
||||
@@ -26,6 +26,7 @@ const optionalStringMax255Schema = z.preprocess(
|
||||
const paymentReceivedFormSchema = z.object({
|
||||
// ── Relations ──
|
||||
job_card: relationFieldSchema,
|
||||
invoice: relationFieldSchema,
|
||||
payment_mode: requiredRelationFieldSchema,
|
||||
customer: requiredRelationFieldSchema,
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ColumnHeader } from "@/shared/data-view/table-view"
|
||||
import { Money } from "@/shared/components/money"
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
/** Core service columns shared between the services page and selector dialogs. */
|
||||
@@ -31,7 +32,7 @@ export const serviceColumns = {
|
||||
header: ({ column }) => <ColumnHeader column={column} title="Price" />,
|
||||
cell: ({ row }) => {
|
||||
const val = (row.original as any).selling_price
|
||||
return val != null ? `$${Number(val).toFixed(2)}` : "—"
|
||||
return val != null ? <Money value={val} /> : "—"
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, ColumnDef<any, any>>
|
||||
|
||||
Reference in New Issue
Block a user