diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx index 749e6b7..041ca38 100644 --- a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx @@ -28,7 +28,7 @@ import { RoofTypeValues, } from "@/app/db/schema/landlord_overrides"; import { CLASSIFIER_FIELDS } from "@/lib/bulkUpload/columnFields"; -import { statusLabel } from "@/lib/bulkUpload/types"; +import { statusLabel, isTerminalStatus } from "@/lib/bulkUpload/types"; // Valid enum options per classifier category, for the editable dropdowns (#299). const CATEGORY_VALUES: Record = { @@ -50,6 +50,10 @@ interface Props { portfolioSlug: string; portfolioId: string; uploadId: string; + // The status at the last server render. Used to refresh the server page exactly + // once when polling first observes a terminal status (async finalise, ADR-0005), + // so the page advances from "Uploading to ARA" to the "Processing complete" card. + serverStatus: string; isDomnaUser: boolean; } @@ -60,10 +64,22 @@ export default function OnboardingProgress({ portfolioSlug, portfolioId, uploadId, + serverStatus, isDomnaUser, }: Props) { const router = useRouter(); - const progress = useBulkUploadProgress(portfolioId, uploadId); + const progress = useBulkUploadProgress(portfolioId, uploadId, { + // When the async finaliser finishes, the poll flips the status to a terminal + // value while the server page is still on `finalising`. Refresh once so the + // server re-renders the "Processing complete" / "failed" card. Guarding on the + // non-terminal serverStatus prevents a refresh loop: after the refresh the + // prop is terminal, so this no-ops. + onSuccess: (data) => { + if (!isTerminalStatus(serverStatus) && isTerminalStatus(data.upload.status)) { + router.refresh(); + } + }, + }); const combine = useRequestCombine(portfolioId, uploadId); const finalize = useFinalize(portfolioId, uploadId); diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx index f28787f..522fc8c 100644 --- a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx @@ -67,6 +67,14 @@ const STATUS_CONFIG = { body: "Matches ready, writing into your portfolio.", cta: false, }, + finalising: { + icon: ArrowPathIcon, + iconBg: "bg-blue-50", + iconColor: "text-blue-500", + title: "Uploading to ARA", + body: "Creating your properties from the matched addresses. This can take a little while for large files.", + cta: false, + }, complete: { icon: CheckCircleIcon, iconBg: "bg-green-50", @@ -167,6 +175,7 @@ export default async function BulkUploadDetailPage(props: { {(statusKey === "processing" || statusKey === "combining" || statusKey === "awaiting_review" || + statusKey === "finalising" || statusKey === "complete" || statusKey === "failed") && upload.taskId && ( @@ -174,6 +183,7 @@ export default async function BulkUploadDetailPage(props: { portfolioSlug={slug} portfolioId={upload.portfolioId} uploadId={uploadId} + serverStatus={upload.status} isDomnaUser={isDomnaUser} /> )} diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx index 281f574..6aaca9c 100644 --- a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx @@ -14,6 +14,7 @@ import { const STATUS_LABELS: Record = { ready_for_processing: { label: "Ready", classes: "bg-amber-100 text-amber-700" }, processing: { label: "Processing", classes: "bg-blue-100 text-blue-700" }, + finalising: { label: "Uploading to ARA", classes: "bg-blue-100 text-blue-700" }, complete: { label: "Complete", classes: "bg-green-100 text-green-700" }, failed: { label: "Failed", classes: "bg-red-100 text-red-700" }, }; diff --git a/src/lib/bulkUpload/client.ts b/src/lib/bulkUpload/client.ts index 44d1d15..2a50023 100644 --- a/src/lib/bulkUpload/client.ts +++ b/src/lib/bulkUpload/client.ts @@ -197,7 +197,11 @@ export function useStartAddressMatching(portfolioId: string, uploadId: string) { }); } -export function useBulkUploadProgress(portfolioId: string, uploadId: string) { +export function useBulkUploadProgress( + portfolioId: string, + uploadId: string, + options?: { onSuccess?: (data: ProgressView) => void }, +) { return useQuery({ queryKey: bulkUploadKeys.progress(uploadId), queryFn: async () => { @@ -211,6 +215,9 @@ export function useBulkUploadProgress(portfolioId: string, uploadId: string) { const status = data?.upload.status; return status && isTerminalStatus(status) ? false : 3000; }, + // v4 onSuccess fires after each successful poll; callers use it to react to a + // status transition (e.g. refresh the server page once it goes terminal). + onSuccess: options?.onSuccess, }); }