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