Add Revit XLSX settings page
This commit is contained in:
parent
1cfeb06ec7
commit
c633e78cd4
15 changed files with 259 additions and 1025 deletions
|
|
@ -1,251 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import {
|
||||
createRevitProjectDevicePrefill,
|
||||
RevitCsvImportPlan,
|
||||
RevitCsvModal,
|
||||
} from "../src/frontend/components/revit-csv-modal.js";
|
||||
import { ProjectDeviceModal } from "../src/frontend/components/project-device-modal.js";
|
||||
import { RoomModal } from "../src/frontend/components/room-modal.js";
|
||||
import {
|
||||
applyExternalInitialImport,
|
||||
getExternalCsvConfiguration,
|
||||
getExternalCircuitListObjects,
|
||||
planExternalInitialImport,
|
||||
previewExternalCsv,
|
||||
updateExternalCsvConfiguration,
|
||||
} from "../src/frontend/utils/api.js";
|
||||
import type { ExternalInitialImportPlanDto } from "../src/frontend/types.js";
|
||||
import { createDefaultExternalCsvConfiguration } from "../src/external-model/csv/external-csv-contracts.js";
|
||||
import { externalCsvTestColumns } from "./fixtures/revit-csv-fixtures.js";
|
||||
|
||||
describe("Revit CSV frontend API", () => {
|
||||
it("uses the project-scoped configuration, preview and import endpoints", async () => {
|
||||
const requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input, init) => {
|
||||
requests.push({
|
||||
url: String(input),
|
||||
method: init?.method ?? "GET",
|
||||
body: init?.body ? JSON.parse(String(init.body)) : null,
|
||||
});
|
||||
return new Response(JSON.stringify({ configuration: null }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
};
|
||||
const configuration = createDefaultExternalCsvConfiguration(externalCsvTestColumns);
|
||||
|
||||
try {
|
||||
await getExternalCsvConfiguration("project-1");
|
||||
await getExternalCircuitListObjects("project-1", "list-1");
|
||||
await updateExternalCsvConfiguration("project-1", 7, configuration);
|
||||
await previewExternalCsv("project-1", "revit.csv", "YWJj");
|
||||
await planExternalInitialImport("project-1", "revit.csv", "YWJj");
|
||||
await applyExternalInitialImport("project-1", {
|
||||
expectedRevision: 8,
|
||||
expectedConfigurationVersion: 2,
|
||||
expectedSha256: "sha",
|
||||
fileName: "revit.csv",
|
||||
contentBase64: "YWJj",
|
||||
sourceName: "Revit-Gesamtmodell",
|
||||
roomDecisions: [{ sourceRoomKey: "101|büro", roomId: "room-1", defaultDistributionBoardId: "board-1" }],
|
||||
familyProjectDeviceDecisions: [{ familyAndType: "Steckdose : Standard", projectDeviceId: "device-1" }],
|
||||
});
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
assert.deepEqual(requests, [
|
||||
{
|
||||
url: "/api/projects/project-1/external-csv/configuration",
|
||||
method: "GET",
|
||||
body: null,
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/circuit-lists/list-1/external-objects",
|
||||
method: "GET",
|
||||
body: null,
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/external-csv/configuration",
|
||||
method: "PUT",
|
||||
body: { expectedRevision: 7, configuration },
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/external-csv/preview",
|
||||
method: "POST",
|
||||
body: { fileName: "revit.csv", contentBase64: "YWJj" },
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/external-csv/initial-import/plan",
|
||||
method: "POST",
|
||||
body: { fileName: "revit.csv", contentBase64: "YWJj" },
|
||||
},
|
||||
{
|
||||
url: "/api/projects/project-1/external-csv/initial-import/apply",
|
||||
method: "POST",
|
||||
body: {
|
||||
expectedRevision: 8,
|
||||
expectedConfigurationVersion: 2,
|
||||
expectedSha256: "sha",
|
||||
fileName: "revit.csv",
|
||||
contentBase64: "YWJj",
|
||||
sourceName: "Revit-Gesamtmodell",
|
||||
roomDecisions: [{ sourceRoomKey: "101|büro", roomId: "room-1", defaultDistributionBoardId: "board-1" }],
|
||||
familyProjectDeviceDecisions: [{ familyAndType: "Steckdose : Standard", projectDeviceId: "device-1" }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Revit CSV modal presentation", () => {
|
||||
it("uses the viewport-wide dialog variant", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
createElement(RevitCsvModal, {
|
||||
currentRevision: 0,
|
||||
onClose: () => undefined,
|
||||
onCreateProjectDevice: async () => ({} as never),
|
||||
onCreateRoom: async () => ({} as never),
|
||||
onRevisionChange: () => undefined,
|
||||
projectId: "project-1",
|
||||
})
|
||||
);
|
||||
|
||||
assert.match(markup, /max-width:calc\(100vw - 2rem\)/);
|
||||
assert.doesNotMatch(markup, /modal-lg/);
|
||||
});
|
||||
|
||||
it("renders the initial import decisions without individual object rows", () => {
|
||||
const plan: ExternalInitialImportPlanDto = {
|
||||
configurationVersion: 2,
|
||||
fileName: "revit.csv",
|
||||
sha256: "sha",
|
||||
byteLength: 100,
|
||||
rowCount: 2,
|
||||
objectCount: 1,
|
||||
passthroughCount: 1,
|
||||
suspectObjectCount: 0,
|
||||
suspectRowNumbers: [],
|
||||
sourceRooms: [{
|
||||
sourceRoomKey: "101|büro",
|
||||
roomNumber: "101",
|
||||
roomName: "Büro",
|
||||
objectCount: 1,
|
||||
suggestedRoomId: "room-1",
|
||||
matchStatus: "exact-number",
|
||||
candidateRoomIds: ["room-1"],
|
||||
}],
|
||||
familyGroups: [{
|
||||
familyAndType: "Steckdose : Standard",
|
||||
objectCount: 1,
|
||||
classified: true,
|
||||
category: "single_phase",
|
||||
connectionKind: null,
|
||||
internalDeviceType: "socket",
|
||||
}],
|
||||
issueCounts: { unknownFamilyAndType: 0, invalidPower: 0, invalidQuantity: 0, missingRoom: 0 },
|
||||
objects: [],
|
||||
existing: {
|
||||
floors: [{ id: "floor-1", name: "EG", sortOrder: 0 }],
|
||||
rooms: [{ id: "room-1", floorId: "floor-1", roomNumber: "101", roomName: "Büro", areaM2: null }],
|
||||
distributionBoards: [{ id: "board-1", name: "UV-01", floorId: "floor-1" }],
|
||||
projectDevices: [{ id: "device-1", name: "Steckdose", displayName: "Steckdose", category: "single_phase", connectionKind: null, powerPerUnit: 100 }],
|
||||
},
|
||||
};
|
||||
const markup = renderToStaticMarkup(createElement(RevitCsvImportPlan, {
|
||||
familyLinks: { "Steckdose : Standard": "device-1" },
|
||||
isApplying: false,
|
||||
onApply: () => undefined,
|
||||
onCreateProjectDevice: () => undefined,
|
||||
onCreateRoom: () => undefined,
|
||||
onFamilyLinkChange: () => undefined,
|
||||
onRoomDecisionChange: () => undefined,
|
||||
plan,
|
||||
roomDecisions: { "101|büro": { roomId: "room-1", defaultDistributionBoardId: "board-1" } },
|
||||
setSourceName: () => undefined,
|
||||
sourceName: "Revit-Gesamtmodell",
|
||||
}));
|
||||
|
||||
assert.match(markup, /Erstimport vorbereiten/);
|
||||
assert.match(markup, /101 · Büro/);
|
||||
assert.match(markup, /Steckdose : Standard/);
|
||||
assert.match(markup, /Erstimport übernehmen/);
|
||||
assert.match(markup, /Raum 101 neu anlegen/);
|
||||
assert.match(markup, /ProjectDevice für Steckdose : Standard neu anlegen/);
|
||||
assert.doesNotMatch(markup, /IFC-GUID/);
|
||||
});
|
||||
|
||||
it("prefills the existing room and ProjectDevice forms from Revit decisions", () => {
|
||||
const roomMarkup = renderToStaticMarkup(createElement(RoomModal, {
|
||||
floors: [{ id: "floor-1", projectId: "project-1", name: "EG", sortOrder: 0 }],
|
||||
initialValues: { roomNumber: "101", roomName: "Büro" },
|
||||
isSaving: false,
|
||||
onClose: () => undefined,
|
||||
onSave: async () => undefined,
|
||||
}));
|
||||
const deviceMarkup = renderToStaticMarkup(createElement(ProjectDeviceModal, {
|
||||
globalDevices: [],
|
||||
initialValues: {
|
||||
name: "socket",
|
||||
displayName: "Steckdose",
|
||||
category: "single_phase",
|
||||
quantity: 2,
|
||||
powerPerUnit: 0.1,
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
isSaving: false,
|
||||
onClose: () => undefined,
|
||||
onImportGlobal: async () => undefined,
|
||||
onSave: async () => undefined,
|
||||
}));
|
||||
|
||||
assert.match(roomMarkup, /value="101"/);
|
||||
assert.match(roomMarkup, /value="Büro"/);
|
||||
assert.match(deviceMarkup, /value="socket"/);
|
||||
assert.match(deviceMarkup, /value="Steckdose"/);
|
||||
assert.match(deviceMarkup, /value="2"/);
|
||||
});
|
||||
|
||||
it("converts mapped Revit watts into ProjectDevice kilowatts", () => {
|
||||
const plan = {
|
||||
familyGroups: [{
|
||||
familyAndType: "Steckdose : Standard",
|
||||
internalDeviceType: "socket",
|
||||
category: "single_phase",
|
||||
connectionKind: "230 V",
|
||||
}],
|
||||
objects: [{
|
||||
sourceValues: { familyAndType: "Steckdose : Standard" },
|
||||
suggestedPlanningValues: {
|
||||
displayName: "Arbeitsplatzsteckdose",
|
||||
effectiveQuantity: 4,
|
||||
powerPerUnitW: 250,
|
||||
simultaneityFactor: 0.8,
|
||||
cosPhi: 0.95,
|
||||
costGroup: "440",
|
||||
remark: "Aus Revit",
|
||||
},
|
||||
}],
|
||||
} as ExternalInitialImportPlanDto;
|
||||
|
||||
assert.deepEqual(
|
||||
createRevitProjectDevicePrefill(plan, "Steckdose : Standard"),
|
||||
{
|
||||
name: "socket",
|
||||
displayName: "Arbeitsplatzsteckdose",
|
||||
connectionKind: "230 V",
|
||||
category: "single_phase",
|
||||
quantity: 4,
|
||||
powerPerUnit: 0.25,
|
||||
simultaneityFactor: 0.8,
|
||||
cosPhi: 0.95,
|
||||
costGroup: "440",
|
||||
remark: "Aus Revit",
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
38
tests/revit-xlsx-settings-api.test.ts
Normal file
38
tests/revit-xlsx-settings-api.test.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, it } from "node:test";
|
||||
import { createDefaultExternalModelConfiguration } from "../src/external-model/configuration/external-model-configuration.js";
|
||||
import { detectExternalModelWorkbook, getExternalModelConfiguration, updateExternalModelConfiguration } from "../src/frontend/utils/api.js";
|
||||
|
||||
describe("Revit XLSX settings", () => {
|
||||
it("uses the project-scoped configuration endpoint with expectedRevision", async () => {
|
||||
const requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input, init) => {
|
||||
requests.push({ url: String(input), method: init?.method ?? "GET", body: init?.body ? JSON.parse(String(init.body)) : undefined });
|
||||
return new Response(JSON.stringify({ configuration: null, history: { currentRevision: 8 } }), { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
};
|
||||
try {
|
||||
const configuration = createDefaultExternalModelConfiguration();
|
||||
await getExternalModelConfiguration("project-1");
|
||||
await updateExternalModelConfiguration("project-1", 7, configuration);
|
||||
await detectExternalModelWorkbook("project-1", new File([new Uint8Array([1, 2])], "sample.xlsx"), configuration);
|
||||
assert.deepEqual(requests, [
|
||||
{ url: "/api/projects/project-1/external-model/configuration", method: "GET", body: undefined },
|
||||
{ url: "/api/projects/project-1/external-model/configuration", method: "PUT", body: { expectedRevision: 7, configuration } },
|
||||
{ url: "/api/projects/project-1/external-model/configuration/detect", method: "POST", body: { fileName: "sample.xlsx", contentBase64: "AQI=", configuration } },
|
||||
]);
|
||||
} finally { globalThis.fetch = originalFetch; }
|
||||
});
|
||||
|
||||
it("exposes the Revit route and removes the old project modal", () => {
|
||||
const projectPage = readFileSync("src/app/projects/[projectId]/page.tsx", "utf8");
|
||||
const revitPage = readFileSync("src/app/projects/[projectId]/revit/page.tsx", "utf8");
|
||||
const settings = readFileSync("src/frontend/components/revit-xlsx-settings-modal.tsx", "utf8");
|
||||
assert.match(projectPage, /href={`\/projects\/\$\{projectId\}\/revit`}/);
|
||||
assert.doesNotMatch(projectPage, /RevitCsvModal|Revit-CSV/);
|
||||
assert.match(revitPage, /Importstatus/);
|
||||
assert.match(settings, /Musterdatei prüfen/);
|
||||
assert.match(settings, /Kombinierte Stromkreisspalte/);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue