mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-08-03 05:18:20 +00:00
chore(preview): throwaway visual harness for the import mapping screen
PREVIEW ONLY — do not merge. Exists so #415's mapping screen can be looked at on a Vercel deployment without a project, a login or a database. One file: src/app/preview/import-mapping/page.tsx, a client page that hands ImportMapping hardcoded fixtures — a client-style export (headers that auto-match, plus a "Priority" column that matches nothing so a needs-review row shows) and two configured workstreams, each with its own stage ladder and assigned contractors. Acme appears under both workstreams as two different assignments, which is the case the value-mapping scoping rule exists for. The rows exercise the branches worth seeing: two spellings of one workstream, an unmatched workstream, a blank contractor, a blank stage and a stage on no ladder. No database, no auth, no gate: it imports neither @/app/db nor loadMappingTargets/loadImportReadiness. Publicly reachable as-is — middleware.ts matches an explicit allowlist that does not include /preview — so no middleware change was needed, which is also why the route sits outside the /projects tree. An on-page banner says the data is fabricated; both exits report themselves rather than being dead buttons. tsc --noEmit and eslint clean. No writes, no DB connection, no Cypress. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f76d5aa167
commit
1eee002403
1 changed files with 180 additions and 0 deletions
180
src/app/preview/import-mapping/page.tsx
Normal file
180
src/app/preview/import-mapping/page.tsx
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"use client";
|
||||
|
||||
/**
|
||||
* ============================================================================
|
||||
* PREVIEW ONLY — THROWAWAY. DO NOT MERGE INTO main.
|
||||
* ============================================================================
|
||||
*
|
||||
* A standalone visual harness for the work-order import mapping screen (#415),
|
||||
* so it can be looked at on a Vercel deployment without a project, a login or a
|
||||
* database.
|
||||
*
|
||||
* Everything below is **fabricated**. There is no query, no session, no gate:
|
||||
* `ImportMapping` is handed props directly. It imports nothing from
|
||||
* `@/app/db`, `loadMappingTargets` or `loadImportReadiness`.
|
||||
*
|
||||
* Reachable without a session: `src/middleware.ts` matches an explicit
|
||||
* allowlist (`/home`, `/portfolio`, `/projects`, `/search`, `/addresses`,
|
||||
* `/due-considerations`, `/eco-spreadsheet`, `/onboarding`), and `/preview` is
|
||||
* not among them, so no middleware change was needed. That is also why this
|
||||
* lives outside the `/projects` tree.
|
||||
*
|
||||
* This file exists only on the `preview/import-mapping` branch and should be
|
||||
* deleted with it.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { ImportMapping } from "@/app/projects/[projectId]/import/components/ImportMapping";
|
||||
import type { ParsedImportResponse } from "@/app/projects/[projectId]/import/components/ImportUpload";
|
||||
import type { ImportMappingTargets } from "@/lib/projects/import/mappingTargets";
|
||||
|
||||
/**
|
||||
* A client's own export, not our template — deliberately imperfect so the
|
||||
* screen shows something to do:
|
||||
* - "Asset Ref" / "Works Order Number" / "Work Type" / "Contractor Name" /
|
||||
* "Forecast End" auto-match by header similarity;
|
||||
* - "Stage" matches exactly;
|
||||
* - "Priority" matches nothing (v1 has no priority field — #426 owns it), so
|
||||
* it lands as *needs review*.
|
||||
*/
|
||||
const HEADERS = [
|
||||
"Asset Ref",
|
||||
"Works Order Number",
|
||||
"Work Type",
|
||||
"Contractor Name",
|
||||
"Stage",
|
||||
"Forecast End",
|
||||
"Priority",
|
||||
];
|
||||
|
||||
/**
|
||||
* One row per (property, workstream), as the import file shape requires. The
|
||||
* values exercise every branch of the value-mapping layer:
|
||||
* - "Window Programme" / "Windows" — two spellings of one workstream, which
|
||||
* merge into a single contractor question;
|
||||
* - "Bathroom Programme" — matches no workstream, so it needs review and its
|
||||
* rows are held back from the contractor and stage tables;
|
||||
* - a blank contractor (an error downstream) and a blank stage (legitimate —
|
||||
* it falls back to the workstream's first stage at commit);
|
||||
* - "Snagging" — a stage that is on no ladder, which is an error.
|
||||
*/
|
||||
const ROWS: string[][] = [
|
||||
["100245", "WO-23881", "Window Programme", "Acme Windows Ltd", "Ordered", "2026-09-30", "Yes"],
|
||||
["100246", "WO-23882", "Window Programme", "Acme Windows Ltd", "Ordered", "2026-09-30", ""],
|
||||
["100247", "WO-23883", "Window Programme", "Sunrise Glazing", "In progress", "2026-10-14", ""],
|
||||
["100248", "WO-23884", "Windows", "Sunrise Glazing", "", "2026-10-21", "Yes"],
|
||||
["100249", "WO-23885", "Windows", "Sunrise Glazing", "Snagging", "2026-11-02", ""],
|
||||
["100250", "WO-23886", "Door Replacement", "Acme Doors Ltd", "Ordered", "2026-09-18", ""],
|
||||
["100251", "WO-23887", "Door Replacement", "Acme Doors Ltd", "Fitted", "2026-09-25", ""],
|
||||
["100252", "WO-23888", "Door Replacement", "", "", "2026-10-01", ""],
|
||||
["100253", "WO-23889", "Door Replacement", "Acme Doors Ltd", "", "", ""],
|
||||
["100254", "WO-23890", "Bathroom Programme", "Riverside Bathrooms", "Ordered", "2026-12-01", "Yes"],
|
||||
["100255", "WO-23891", "Bathroom Programme", "Riverside Bathrooms", "", "2026-12-08", ""],
|
||||
["100256", "WO-23892", "Window Programme", "Acme Windows Ltd", "Completed", "2026-08-12", ""],
|
||||
["100257", "WO-23893", "Windows", "Acme Windows Ltd", "Charged", "2026-08-19", ""],
|
||||
["100258", "WO-23894", "Door Replacement", "Acme Doors Ltd", "Completed", "2026-08-26", ""],
|
||||
];
|
||||
|
||||
const FILE: ParsedImportResponse = {
|
||||
filename: "stonewater_planned_works_2026.xlsx",
|
||||
headers: HEADERS,
|
||||
rows: ROWS,
|
||||
rowCount: ROWS.length,
|
||||
preview: ROWS.slice(0, 10),
|
||||
};
|
||||
|
||||
/**
|
||||
* What the project would have been configured with by the setup wizard
|
||||
* (#410/#411/#413): two workstreams, each with its own stage ladder and its own
|
||||
* assigned contractors. Note that Acme appears under both workstreams as two
|
||||
* *different* assignments — the case the scoping rule exists for.
|
||||
*/
|
||||
const TARGETS: ImportMappingTargets = {
|
||||
workstreams: [
|
||||
{
|
||||
id: "1",
|
||||
workstreamId: "10",
|
||||
name: "Doors",
|
||||
stages: [
|
||||
{ id: "101", name: "Ordered", order: 1 },
|
||||
{ id: "102", name: "Fitted", order: 2 },
|
||||
{ id: "103", name: "Completed", order: 3 },
|
||||
],
|
||||
contractors: [
|
||||
{ id: "201", organisationId: "org-doors", name: "Acme Doors Ltd" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
workstreamId: "11",
|
||||
name: "Windows",
|
||||
stages: [
|
||||
{ id: "111", name: "Ordered", order: 1 },
|
||||
{ id: "112", name: "In progress", order: 2 },
|
||||
{ id: "113", name: "Completed", order: 3 },
|
||||
{ id: "114", name: "Charged", order: 4 },
|
||||
{ id: "115", name: "Closed", order: 5 },
|
||||
],
|
||||
contractors: [
|
||||
{ id: "211", organisationId: "org-acme", name: "Acme Windows Ltd" },
|
||||
{ id: "212", organisationId: "org-sunrise", name: "Sunrise Glazing" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function ImportMappingPreviewPage() {
|
||||
// The preview has nowhere to navigate to, so both exits report themselves
|
||||
// rather than being dead buttons.
|
||||
const [lastAction, setLastAction] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-6 py-10">
|
||||
<div
|
||||
role="note"
|
||||
className="mb-6 rounded-xl border border-purple-200 bg-purple-50 px-4 py-3"
|
||||
>
|
||||
<p className="text-sm font-semibold text-purple-900">
|
||||
Preview only — this data is fabricated
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-purple-800">
|
||||
A throwaway harness for the work-order import mapping screen (issue
|
||||
#415). The file, its rows and the project's workstreams, stages
|
||||
and contractors are all hardcoded in this page — nothing is read from
|
||||
the database and nothing can be saved. Try re-pointing a column, or
|
||||
unmapping a required one to see the value tables lock.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<p className="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-1">
|
||||
Work-order import · Step 2 of 3
|
||||
</p>
|
||||
<h1 className="text-3xl font-extrabold text-gray-900 tracking-tight">
|
||||
Map columns
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{lastAction && (
|
||||
<p className="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 text-xs text-gray-600">
|
||||
{lastAction}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<ImportMapping
|
||||
file={FILE}
|
||||
targets={TARGETS}
|
||||
onBack={() =>
|
||||
setLastAction(
|
||||
"“Back to upload” — in the real screen this returns to the drop zone with the parsed file intact.",
|
||||
)
|
||||
}
|
||||
onContinue={(handoff) =>
|
||||
setLastAction(
|
||||
`“Continue to validation” — the import session is complete (${handoff.warningRows} row(s) would fail validation). Validation and commit arrive in #417.`,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue