refactor(parcel-sync): global UAT bar, connection pill, reorder tabs

- UAT autocomplete always visible above tabs (all tabs share it)
- Connection status pill in top-right: breathing green dot when connected,
  dropdown with credentials form / disconnect button
- Tab order: Cautare Parcele (1st) -> Catalog Layere -> Export (last)
- Renamed 'Butonul Magic' to just 'Magic'
- Removed connection/UAT cards from inside Export tab
This commit is contained in:
AI Assistant
2026-03-06 18:41:11 +02:00
parent 86edbdf44e
commit 129b62758c
3 changed files with 613 additions and 550 deletions
+33 -73
View File
@@ -35,8 +35,7 @@ const validate = (body: ExportBundleRequest) => {
return { username, password, siruta, jobId, mode }; return { username, password, siruta, jobId, mode };
}; };
const sleep = (ms: number) => const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
new Promise((resolve) => setTimeout(resolve, ms));
const scheduleClear = (jobId?: string) => { const scheduleClear = (jobId?: string) => {
if (!jobId) return; if (!jobId) return;
@@ -112,11 +111,7 @@ export async function POST(req: Request) {
pushProgress(); pushProgress();
}; };
const setPhaseState = ( const setPhaseState = (next: string, weight: number, nextTotal?: number) => {
next: string,
weight: number,
nextTotal?: number,
) => {
phase = next; phase = next;
currentWeight = weight; currentWeight = weight;
phaseTotal = nextTotal; phaseTotal = nextTotal;
@@ -145,7 +140,7 @@ export async function POST(req: Request) {
updateOverall(0); updateOverall(0);
}; };
const withHeartbeat = async <T,>(task: () => Promise<T>) => { const withHeartbeat = async <T>(task: () => Promise<T>) => {
let tick = 0.1; let tick = 0.1;
updatePhaseProgress(tick, 1); updatePhaseProgress(tick, 1);
const interval = setInterval(() => { const interval = setInterval(() => {
@@ -284,7 +279,7 @@ export async function POST(req: Request) {
let lastRequest = 0; let lastRequest = 0;
const minInterval = 250; const minInterval = 250;
const throttled = async <T,>(fn: () => Promise<T>) => { const throttled = async <T>(fn: () => Promise<T>) => {
let attempt = 0; let attempt = 0;
while (true) { while (true) {
const now = Date.now(); const now = Date.now();
@@ -330,7 +325,11 @@ export async function POST(req: Request) {
const normalizeIntravilan = (values: string[]) => { const normalizeIntravilan = (values: string[]) => {
const normalized = values const normalized = values
.map((v) => String(v ?? "").trim().toLowerCase()) .map((v) =>
String(v ?? "")
.trim()
.toLowerCase(),
)
.filter(Boolean); .filter(Boolean);
const unique = new Set(normalized); const unique = new Set(normalized);
if (!unique.size) return "-"; if (!unique.size) return "-";
@@ -359,8 +358,7 @@ export async function POST(req: Request) {
const address = item?.immovableAddresses?.[0]?.address ?? null; const address = item?.immovableAddresses?.[0]?.address ?? null;
if (!address) return "-"; if (!address) return "-";
const parts: string[] = []; const parts: string[] = [];
if (address.addressDescription) if (address.addressDescription) parts.push(address.addressDescription);
parts.push(address.addressDescription);
if (address.street) parts.push(`Str. ${address.street}`); if (address.street) parts.push(`Str. ${address.street}`);
if (address.buildingNo) parts.push(`Nr. ${address.buildingNo}`); if (address.buildingNo) parts.push(`Nr. ${address.buildingNo}`);
if (address.locality?.name) parts.push(address.locality.name); if (address.locality?.name) parts.push(address.locality.name);
@@ -368,18 +366,12 @@ export async function POST(req: Request) {
}; };
/* Building cross-ref map */ /* Building cross-ref map */
const buildingMap = new Map< const buildingMap = new Map<string, { has: boolean; legal: boolean }>();
string,
{ has: boolean; legal: boolean }
>();
for (const feature of cladiriFeatures) { for (const feature of cladiriFeatures) {
const attrs = feature.attributes ?? {}; const attrs = feature.attributes ?? {};
const immovableId = const immovableId = attrs.IMMOVABLE_ID ?? attrs.IMOVABLE_ID ?? null;
attrs.IMMOVABLE_ID ?? attrs.IMOVABLE_ID ?? null;
const workspaceId = attrs.WORKSPACE_ID ?? null; const workspaceId = attrs.WORKSPACE_ID ?? null;
const baseRef = baseCadRef( const baseRef = baseCadRef(attrs.NATIONAL_CADASTRAL_REFERENCE ?? "");
attrs.NATIONAL_CADASTRAL_REFERENCE ?? "",
);
const isLegal = const isLegal =
Number(attrs.IS_LEGAL ?? 0) === 1 || Number(attrs.IS_LEGAL ?? 0) === 1 ||
String(attrs.IS_LEGAL ?? "").toLowerCase() === "true"; String(attrs.IS_LEGAL ?? "").toLowerCase() === "true";
@@ -408,8 +400,7 @@ export async function POST(req: Request) {
const addOwner = (landbook: string, name: string) => { const addOwner = (landbook: string, name: string) => {
if (!landbook || !name) return; if (!landbook || !name) return;
const existing = const existing = ownersByLandbook.get(landbook) ?? new Set<string>();
ownersByLandbook.get(landbook) ?? new Set<string>();
existing.add(name); existing.add(name);
ownersByLandbook.set(landbook, existing); ownersByLandbook.set(landbook, existing);
}; };
@@ -444,9 +435,7 @@ export async function POST(req: Request) {
(listResponse?.content ?? []).forEach((item: any) => { (listResponse?.content ?? []).forEach((item: any) => {
const idKey = normalizeId(item?.immovablePk); const idKey = normalizeId(item?.immovablePk);
if (idKey) immovableListById.set(idKey, item); if (idKey) immovableListById.set(idKey, item);
const cadKey = normalizeCadRef( const cadKey = normalizeCadRef(item?.identifierDetails ?? "");
item?.identifierDetails ?? "",
);
if (cadKey) immovableListByCad.set(cadKey, item); if (cadKey) immovableListByCad.set(cadKey, item);
}); });
listPage += 1; listPage += 1;
@@ -499,10 +488,7 @@ export async function POST(req: Request) {
]; ];
csvRows.push(headers.join(",")); csvRows.push(headers.join(","));
const detailsByObjectId = new Map< const detailsByObjectId = new Map<string, Record<string, unknown>>();
string,
Record<string, unknown>
>();
for (let index = 0; index < terenuriFeatures.length; index += 1) { for (let index = 0; index < terenuriFeatures.length; index += 1) {
const feature = terenuriFeatures[index]!; const feature = terenuriFeatures[index]!;
@@ -533,15 +519,11 @@ export async function POST(req: Request) {
); );
immAppsCache.set(appKey, apps); immAppsCache.set(appKey, apps);
} }
const chosen = pickApplication( const chosen = pickApplication(apps, Number(applicationId ?? 0));
apps,
Number(applicationId ?? 0),
);
const appId = const appId =
chosen?.applicationId ?? chosen?.applicationId ??
(applicationId ? Number(applicationId) : null); (applicationId ? Number(applicationId) : null);
solicitant = solicitant = chosen?.solicitant ?? chosen?.deponent ?? solicitant;
chosen?.solicitant ?? chosen?.deponent ?? solicitant;
if (appId) { if (appId) {
const folKey = `${workspaceId}:${immovableId}:${appId}`; const folKey = `${workspaceId}:${immovableId}:${appId}`;
@@ -563,8 +545,7 @@ export async function POST(req: Request) {
} }
} }
const cadRefRaw = (attrs.NATIONAL_CADASTRAL_REFERENCE ?? const cadRefRaw = (attrs.NATIONAL_CADASTRAL_REFERENCE ?? "") as string;
"") as string;
const cadRef = normalizeCadRef(cadRefRaw); const cadRef = normalizeCadRef(cadRefRaw);
const immKey = normalizeId(immovableId); const immKey = normalizeId(immovableId);
const listItem = const listItem =
@@ -573,51 +554,37 @@ export async function POST(req: Request) {
const docKey = listItem?.immovablePk const docKey = listItem?.immovablePk
? normalizeId(listItem.immovablePk) ? normalizeId(listItem.immovablePk)
: ""; : "";
const docItem = docKey const docItem = docKey ? docByImmovable.get(docKey) : undefined;
? docByImmovable.get(docKey)
: undefined;
const landbookIE = docItem?.landbookIE ?? ""; const landbookIE = docItem?.landbookIE ?? "";
const owners = const owners =
landbookIE && ownersByLandbook.get(String(landbookIE)) landbookIE && ownersByLandbook.get(String(landbookIE))
? Array.from( ? Array.from(ownersByLandbook.get(String(landbookIE)) ?? [])
ownersByLandbook.get(String(landbookIE)) ?? [],
)
: []; : [];
const ownersByCad = const ownersByCad =
cadRefRaw && ownersByLandbook.get(String(cadRefRaw)) cadRefRaw && ownersByLandbook.get(String(cadRefRaw))
? Array.from( ? Array.from(ownersByLandbook.get(String(cadRefRaw)) ?? [])
ownersByLandbook.get(String(cadRefRaw)) ?? [],
)
: []; : [];
proprietari = proprietari =
Array.from(new Set([...owners, ...ownersByCad])).join( Array.from(new Set([...owners, ...ownersByCad])).join("; ") ||
"; ", proprietari;
) || proprietari;
nrCF = nrCF =
docItem?.landbookIE || docItem?.landbookIE ||
listItem?.paperLbNo || listItem?.paperLbNo ||
listItem?.paperCadNo || listItem?.paperCadNo ||
nrCF; nrCF;
const nrCFVechiRaw = const nrCFVechiRaw = listItem?.paperLbNo || listItem?.paperCadNo || "";
listItem?.paperLbNo || listItem?.paperCadNo || "";
nrCFVechi = nrCFVechi =
docItem?.landbookIE && nrCFVechiRaw !== nrCF docItem?.landbookIE && nrCFVechiRaw !== nrCF
? nrCFVechiRaw ? nrCFVechiRaw
: nrCFVechi; : nrCFVechi;
nrTopo = nrTopo =
listItem?.topNo || listItem?.topNo || docItem?.topNo || listItem?.paperCadNo || nrTopo;
docItem?.topNo || addressText = listItem ? formatAddress(listItem) : addressText;
listItem?.paperCadNo ||
nrTopo;
addressText = listItem
? formatAddress(listItem)
: addressText;
const parcelRef = baseCadRef(cadRefRaw); const parcelRef = baseCadRef(cadRefRaw);
const wKey = makeWorkspaceKey(workspaceId, immovableId); const wKey = makeWorkspaceKey(workspaceId, immovableId);
const build = const build = (immKey ? buildingMap.get(immKey) : undefined) ??
(immKey ? buildingMap.get(immKey) : undefined) ??
(wKey ? buildingMap.get(wKey) : undefined) ?? (wKey ? buildingMap.get(wKey) : undefined) ??
(parcelRef ? buildingMap.get(parcelRef) : undefined) ?? { (parcelRef ? buildingMap.get(parcelRef) : undefined) ?? {
has: false, has: false,
@@ -637,10 +604,8 @@ export async function POST(req: Request) {
NR_TOPO: nrTopo, NR_TOPO: nrTopo,
ADRESA: addressText, ADRESA: addressText,
PROPRIETARI: proprietari, PROPRIETARI: proprietari,
SUPRAFATA_2D: SUPRAFATA_2D: areaValue !== null ? Number(areaValue.toFixed(2)) : "",
areaValue !== null ? Number(areaValue.toFixed(2)) : "", SUPRAFATA_R: areaValue !== null ? Math.round(areaValue) : "",
SUPRAFATA_R:
areaValue !== null ? Math.round(areaValue) : "",
SOLICITANT: solicitant, SOLICITANT: solicitant,
INTRAVILAN: intravilan, INTRAVILAN: intravilan,
CATEGORIE_FOLOSINTA: categorie, CATEGORIE_FOLOSINTA: categorie,
@@ -671,8 +636,7 @@ export async function POST(req: Request) {
]; ];
csvRows.push(row.join(",")); csvRows.push(row.join(","));
if (index % 10 === 0) if (index % 10 === 0) updatePhaseProgress(index + 1, terenuriCount);
updatePhaseProgress(index + 1, terenuriCount);
} }
updatePhaseProgress(terenuriFeatures.length, terenuriCount); updatePhaseProgress(terenuriFeatures.length, terenuriCount);
@@ -737,9 +701,7 @@ export async function POST(req: Request) {
]), ]),
); );
const magicFeatures = terenuriGeo.features.map((feature) => { const magicFeatures = terenuriGeo.features.map((feature) => {
const objectId = String( const objectId = String(feature.properties?.OBJECTID ?? "");
feature.properties?.OBJECTID ?? "",
);
const extra = detailsByObjectId.get(objectId) ?? {}; const extra = detailsByObjectId.get(objectId) ?? {};
return { return {
...feature, ...feature,
@@ -870,9 +832,7 @@ export async function POST(req: Request) {
scheduleClear(jobId); scheduleClear(jobId);
const lower = errMessage.toLowerCase(); const lower = errMessage.toLowerCase();
const statusCode = const statusCode =
lower.includes("login failed") || lower.includes("session") lower.includes("login failed") || lower.includes("session") ? 401 : 400;
? 401
: 400;
return new Response(JSON.stringify({ error: errMessage }), { return new Response(JSON.stringify({ error: errMessage }), {
status: statusCode, status: statusCode,
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
+7 -21
View File
@@ -75,11 +75,7 @@ export async function POST(req: Request) {
pushProgress(); pushProgress();
}; };
const setPhaseState = ( const setPhaseState = (next: string, weight: number, nextTotal?: number) => {
next: string,
weight: number,
nextTotal?: number,
) => {
phase = next; phase = next;
currentWeight = weight; currentWeight = weight;
phaseTotal = nextTotal; phaseTotal = nextTotal;
@@ -108,7 +104,7 @@ export async function POST(req: Request) {
updateOverall(0); updateOverall(0);
}; };
const withHeartbeat = async <T,>(task: () => Promise<T>) => { const withHeartbeat = async <T>(task: () => Promise<T>) => {
let tick = 0.1; let tick = 0.1;
updatePhaseProgress(tick, 1); updatePhaseProgress(tick, 1);
const interval = setInterval(() => { const interval = setInterval(() => {
@@ -161,8 +157,7 @@ export async function POST(req: Request) {
count = await client.countLayer(layer, validated.siruta); count = await client.countLayer(layer, validated.siruta);
} }
} catch (error) { } catch (error) {
const msg = const msg = error instanceof Error ? error.message : "Count error";
error instanceof Error ? error.message : "Count error";
if (!msg.toLowerCase().includes("count unavailable")) throw error; if (!msg.toLowerCase().includes("count unavailable")) throw error;
} }
updatePhaseProgress(2, 2); updatePhaseProgress(2, 2);
@@ -187,9 +182,7 @@ export async function POST(req: Request) {
onProgress: (value, totalCount) => { onProgress: (value, totalCount) => {
updatePhaseProgress( updatePhaseProgress(
value, value,
typeof totalCount === "number" typeof totalCount === "number" ? totalCount : value + pageSize,
? totalCount
: value + pageSize,
); );
}, },
}) })
@@ -200,16 +193,11 @@ export async function POST(req: Request) {
onProgress: (value, totalCount) => { onProgress: (value, totalCount) => {
updatePhaseProgress( updatePhaseProgress(
value, value,
typeof totalCount === "number" typeof totalCount === "number" ? totalCount : value + pageSize,
? totalCount
: value + pageSize,
); );
}, },
}); });
updatePhaseProgress( updatePhaseProgress(features.length, count ?? features.length);
features.length,
count ?? features.length,
);
finishPhase(); finishPhase();
/* Fields */ /* Fields */
@@ -274,9 +262,7 @@ export async function POST(req: Request) {
scheduleClear(jobId); scheduleClear(jobId);
const lower = errMessage.toLowerCase(); const lower = errMessage.toLowerCase();
const statusCode = const statusCode =
lower.includes("login failed") || lower.includes("session") lower.includes("login failed") || lower.includes("session") ? 401 : 400;
? 401
: 400;
return new Response(JSON.stringify({ error: errMessage }), { return new Response(JSON.stringify({ error: errMessage }), {
status: statusCode, status: statusCode,
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
File diff suppressed because it is too large Load Diff