From 05b55b572176a836402825b1f39b30d5c6b91bd7 Mon Sep 17 00:00:00 2001 From: humam kerdiah Date: Wed, 13 May 2026 17:20:59 +0400 Subject: [PATCH] fix: add staleTime to job card expense, parts, and services queries; update job card general info and payments received components --- .../job-cards/[id]/expense-items/page.tsx | 1 + .../sales/job-cards/[id]/page.tsx | 15 +-- .../sales/job-cards/[id]/parts/page.tsx | 1 + .../sales/job-cards/[id]/services/page.tsx | 1 + apps/dashboard/app/layout.tsx | 2 + .../dashboard-details-page-layout.tsx | 1 + .../modules/estimates/estimate-form.tsx | 44 +++++++-- .../job-cards/job-card-general-info.tsx | 14 ++- .../job-cards/job-card-payments-received.tsx | 14 ++- .../payment-received-form.tsx | 95 ++++++++++++------- apps/dashboard/package.json | 1 + pnpm-lock.yaml | 76 +++++++++------ 12 files changed, 174 insertions(+), 91 deletions(-) diff --git a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/expense-items/page.tsx b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/expense-items/page.tsx index e9bc333..7df0650 100644 --- a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/expense-items/page.tsx +++ b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/expense-items/page.tsx @@ -44,6 +44,7 @@ export default function JobCardExpenseItemsPage({ const { data, isLoading } = useQuery({ queryKey, queryFn: () => api.jobCards.getExpenseItems(jobCardId), + staleTime: 30_000, }) const rows = (data as any)?.data ?? [] diff --git a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/page.tsx b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/page.tsx index 99cfd93..9e5abfb 100644 --- a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/page.tsx +++ b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/page.tsx @@ -1,21 +1,10 @@ -import { getServerApi } from '@garage/api/server' import { JobCardGeneralInfo } from '@/modules/job-cards/job-card-general-info' import DashboardPage from '@/base/components/layout/dashboard/dashboard-page' -import type { JobCardShowData } from '@garage/api' - -export default async function JobCardDetailPage(props: { params: Promise<{ id: string }> }) { - const { id } = await props.params - const api = await getServerApi() - const response = await api.jobCards.show(id) - const data = response.data - - if (!data) { - return
Job card not found.
- } +export default async function JobCardDetailPage() { return ( - + ) } diff --git a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/parts/page.tsx b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/parts/page.tsx index bf52d14..6a0ef4a 100644 --- a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/parts/page.tsx +++ b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/parts/page.tsx @@ -44,6 +44,7 @@ export default function JobCardPartsPage({ const { data, isLoading } = useQuery({ queryKey, queryFn: () => api.jobCards.getParts(jobCardId), + staleTime: 30_000, }) const rows = (data as any)?.data ?? [] diff --git a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/services/page.tsx b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/services/page.tsx index 1f8accb..59bf6ed 100644 --- a/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/services/page.tsx +++ b/apps/dashboard/app/(authenticated)/sales/job-cards/[id]/services/page.tsx @@ -46,6 +46,7 @@ export default function JobCardServicesPage({ const { data, isLoading } = useQuery({ queryKey, queryFn: () => api.jobCards.getServices(jobCardId), + staleTime: 30_000, }) const rows = (data as any)?.data ?? [] diff --git a/apps/dashboard/app/layout.tsx b/apps/dashboard/app/layout.tsx index 19e7703..7249e80 100644 --- a/apps/dashboard/app/layout.tsx +++ b/apps/dashboard/app/layout.tsx @@ -1,4 +1,5 @@ import { Geist_Mono, Inter } from "next/font/google" +import NextTopLoader from "nextjs-toploader" import { QueryProvider } from "@/shared/components/query-provider" @@ -33,6 +34,7 @@ export default function RootLayout({ className={cn("antialiased", fontMono.variable, "font-sans", inter.variable)} > + {children} diff --git a/apps/dashboard/base/components/layout/dashboard/dashboard-details-page-layout.tsx b/apps/dashboard/base/components/layout/dashboard/dashboard-details-page-layout.tsx index d15a816..b77c428 100644 --- a/apps/dashboard/base/components/layout/dashboard/dashboard-details-page-layout.tsx +++ b/apps/dashboard/base/components/layout/dashboard/dashboard-details-page-layout.tsx @@ -109,6 +109,7 @@ export default function DashboardDetailsPageLayout({ = { cancelled: "destructive", } -export function JobCardGeneralInfo({ jobCard }: { jobCard: JobCard }) { +export function JobCardGeneralInfo({ jobCard: jobCardProp }: { jobCard?: JobCard } = {}) { + const jobCardFromContext = useJobCard() + const jobCard = (jobCardProp ?? jobCardFromContext) as JobCard + + if (!jobCard) { + return
Job card not found.
+ } + const formatStatus = (status?: string) => { if (!status) return null return status diff --git a/apps/dashboard/modules/job-cards/job-card-payments-received.tsx b/apps/dashboard/modules/job-cards/job-card-payments-received.tsx index cffb145..e9f996e 100644 --- a/apps/dashboard/modules/job-cards/job-card-payments-received.tsx +++ b/apps/dashboard/modules/job-cards/job-card-payments-received.tsx @@ -17,12 +17,20 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/shared/co import { Button } from "@/shared/components/ui/button" import { useJobCard } from "./job-card-context" import { formatDate, formatCurrency } from "@/shared/utils/formatters" +import { useState } from "react" export default function JobCardPaymentsReceived() { const jobCard = useJobCard() + const [hasOpened, setHasOpened] = useState(false) return ( - + { + if (open) setHasOpened(true) + }} + >
@@ -36,6 +44,7 @@ export default function JobCardPaymentsReceived() { + {hasOpened && ( extraParams={{ job_card_id: jobCard?.id }} routeKey={PAYMENT_RECEIVED_ROUTES.INDEX} @@ -48,6 +57,8 @@ export default function JobCardPaymentsReceived() { resourceId={resourceId} defaultJobCard={{ id: jobCard?.id, title: jobCard?.title }} invoiceCustomer={jobCard?.customer as any} + lockJobCard + lockCustomer onSuccess={invalidateQuery} /> )} @@ -127,6 +138,7 @@ export default function JobCardPaymentsReceived() { actionsColumn(), ]} /> + )} diff --git a/apps/dashboard/modules/payment-received/payment-received-form.tsx b/apps/dashboard/modules/payment-received/payment-received-form.tsx index f134775..436a89f 100644 --- a/apps/dashboard/modules/payment-received/payment-received-form.tsx +++ b/apps/dashboard/modules/payment-received/payment-received-form.tsx @@ -5,7 +5,8 @@ import { AlertTriangle, Plus, Save } from "lucide-react" import { Button } from "@/shared/components/ui/button" import { Alert, AlertTitle } from "@/shared/components/ui/alert" -import { FieldGroup } from "@/shared/components/ui/field" +import { Field, FieldGroup, FieldLabel } from "@/shared/components/ui/field" +import { Input } from "@/shared/components/ui/input" import { Rhform, RhfTextField, @@ -36,6 +37,8 @@ export type PaymentReceivedFormProps = { invoiceId?: string | null invoiceCustomer?: { id?: number | null; first_name?: string | null; last_name?: string | null } | null invoiceAmount?: number | string | null + lockJobCard?: boolean + lockCustomer?: boolean } // ── Default values ── @@ -90,27 +93,33 @@ const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) = // ── Component ── -export function PaymentReceivedForm({ resourceId, initialData, onSuccess, defaultJobCard, invoiceId, invoiceCustomer, invoiceAmount }: PaymentReceivedFormProps) { +export function PaymentReceivedForm({ resourceId, initialData, onSuccess, defaultJobCard, invoiceId, invoiceCustomer, invoiceAmount, lockJobCard, lockCustomer }: PaymentReceivedFormProps) { const api = useAuthApi() + const isJobCardLocked = !resourceId && (lockJobCard ?? !!defaultJobCard?.id) + const isCustomerLocked = !resourceId && (lockCustomer ?? !!invoiceCustomer?.id) + + const customerLabel = invoiceCustomer?.first_name + ? `${invoiceCustomer.first_name} ${invoiceCustomer.last_name || ""}`.trim() + : (invoiceCustomer as any)?.company_name || (invoiceCustomer as any)?.name || "" + const resolvedInitialData = useMemo(() => { const base: any = { ...(initialData as any) } if (!resourceId) { if (defaultJobCard?.id != null) { - base.job_card = toRelation(defaultJobCard.id, defaultJobCard.title ?? undefined) + base.job_card_id = defaultJobCard.id + base.job_card_name = defaultJobCard.title ?? undefined } if (invoiceCustomer?.id != null) { - const customerLabel = invoiceCustomer.first_name - ? `${invoiceCustomer.first_name} ${invoiceCustomer.last_name || ""}`.trim() - : (invoiceCustomer as any).company_name || (invoiceCustomer as any).name || undefined - base.customer = toRelation(invoiceCustomer.id, customerLabel) + base.customer_id = invoiceCustomer.id + base.customer_name = customerLabel } if (invoiceAmount != null && invoiceAmount !== "") { base.amount_received = Number(invoiceAmount) } } return Object.keys(base).length ? base : initialData - }, [resourceId, defaultJobCard, initialData, invoiceCustomer, invoiceAmount]) + }, [resourceId, defaultJobCard, initialData, invoiceCustomer, invoiceAmount, customerLabel]) const { form, isEditing } = useResourceForm({ schema: paymentReceivedFormSchema, @@ -153,33 +162,49 @@ export function PaymentReceivedForm({ resourceId, initialData, onSuccess, defaul
- api.customers.list()} - mapOption={(item: any) => ({ - value: String(item.id), - label: item.first_name - ? `${item.first_name} ${item.last_name || ""}`.trim() - : item.name || `#${item.id}`, - })} - {...STORE_OBJECT} - /> - api.jobCards.list()} - mapOption={(item: any) => ({ - value: String(item.id), - label: item.title, - })} - {...STORE_OBJECT} - /> + {isCustomerLocked ? ( + + + Customer* + + + + ) : ( + api.customers.list()} + mapOption={(item: any) => ({ + value: String(item.id), + label: item.first_name + ? `${item.first_name} ${item.last_name || ""}`.trim() + : item.name || `#${item.id}`, + })} + {...STORE_OBJECT} + /> + )} + {isJobCardLocked ? ( + + Job Card + + + ) : ( + api.jobCards.list()} + mapOption={(item: any) => ({ + value: String(item.id), + label: item.title, + })} + {...STORE_OBJECT} + /> + )}
diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index cca0b05..6b8f309 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -31,6 +31,7 @@ "lucide-react": "^0.577.0", "next": "16.1.7", "next-themes": "^0.4.6", + "nextjs-toploader": "^3.9.17", "nuqs": "^2.8.9", "object-to-formdata": "^4.5.1", "radix-ui": "^1.4.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c78c6ed..3b80d52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + nextjs-toploader: + specifier: ^3.9.17 + version: 3.9.17(next@16.1.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nuqs: specifier: ^2.8.9 version: 2.8.9(next@16.1.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) @@ -3707,6 +3710,13 @@ packages: sass: optional: true + nextjs-toploader@3.9.17: + resolution: {integrity: sha512-9OF0KSSLtoSAuNg2LZ3aTl4hR9mBDj5L9s9DZiFCbMlXehyICGjkIz5dVGzuATU2bheJZoBdFgq9w07AKSuQQw==} + peerDependencies: + next: '>= 6.0.0' + react: '>= 16.0.0' + react-dom: '>= 16.0.0' + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -3727,6 +3737,9 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + nuqs@2.8.9: resolution: {integrity: sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ==} peerDependencies: @@ -4921,7 +4934,7 @@ snapshots: '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5079,7 +5092,7 @@ snapshots: '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -5189,7 +5202,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5197,7 +5210,7 @@ snapshots: '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -5213,7 +5226,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -5227,7 +5240,7 @@ snapshots: '@eslint/eslintrc@3.3.5': dependencies: ajv: 6.14.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -6557,7 +6570,7 @@ snapshots: '@typescript-eslint/types': 8.50.0 '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: @@ -6569,7 +6582,7 @@ snapshots: '@typescript-eslint/types': 8.50.0 '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -6579,7 +6592,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.2) '@typescript-eslint/types': 8.50.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -6588,7 +6601,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) '@typescript-eslint/types': 8.50.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6611,7 +6624,7 @@ snapshots: '@typescript-eslint/types': 8.50.0 '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2) '@typescript-eslint/utils': 8.50.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.2) - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 @@ -6623,7 +6636,7 @@ snapshots: '@typescript-eslint/types': 8.50.0 '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) '@typescript-eslint/utils': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.4(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 @@ -6638,7 +6651,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.2) '@typescript-eslint/types': 8.50.0 '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -6653,7 +6666,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) '@typescript-eslint/types': 8.50.0 '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -6936,7 +6949,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 @@ -7529,7 +7542,7 @@ snapshots: eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.4(jiti@2.6.1) get-tsconfig: 4.13.7 is-bun-module: 2.0.0 @@ -7693,7 +7706,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -7734,7 +7747,7 @@ snapshots: ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -7846,7 +7859,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -7944,7 +7957,7 @@ snapshots: finalhandler@2.1.1: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -8148,13 +8161,6 @@ snapshots: jsprim: 2.0.2 sshpk: 1.18.0 - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -8696,6 +8702,14 @@ snapshots: - '@babel/core' - babel-plugin-macros + nextjs-toploader@3.9.17(next@16.1.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + next: 16.1.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + nprogress: 0.2.0 + prop-types: 15.8.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + node-domexception@1.0.0: {} node-fetch@3.3.2: @@ -8715,6 +8729,8 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 + nprogress@0.2.0: {} + nuqs@2.8.9(next@16.1.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): dependencies: '@standard-schema/spec': 1.0.0 @@ -9244,7 +9260,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -9295,7 +9311,7 @@ snapshots: send@1.2.1: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -9363,7 +9379,7 @@ snapshots: fast-glob: 3.3.3 fs-extra: 11.3.4 fuzzysort: 3.1.0 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) kleur: 4.1.5 msw: 2.12.14(@types/node@25.5.0)(typescript@5.9.3) node-fetch: 3.3.2