#50: /site-finder/compare — shortlist (localStorage, SSR-guard, cap 2-5) + CompareTable (per-row winner-highlight, reuse useParcelAnalyzeQuery per cad, hook-order stable в .map, cache-shared). Cross-link с site-finder. #65: debounced search (300ms, client-side filter top-15) в DeveloperLeaderboard + cross-link объект→Site Finder analyze. Existing developers compare не дублирован. Closes #50 Closes #65
60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
/**
|
||
* Site Finder multi-cad compare — localStorage-backed shortlist (issue #50).
|
||
*
|
||
* The compare page lets a developer pin 2–5 cadastral numbers and view their
|
||
* analyze metrics side-by-side. The shortlist survives reload via localStorage
|
||
* (per browser, not per server session) — mirroring `gd_recent_parcels` in
|
||
* site-finder-api.ts. All access is SSR-guarded (`typeof window`).
|
||
*/
|
||
|
||
const STORAGE_KEY = "gd_compare_shortlist";
|
||
|
||
/** Hard cap from the issue: batch analyze runs max 5 parcels in parallel. */
|
||
export const COMPARE_MAX = 5;
|
||
/** Need at least two parcels for a meaningful side-by-side. */
|
||
export const COMPARE_MIN = 2;
|
||
|
||
/**
|
||
* Validates cadastral number formats (same regex as CadInput):
|
||
* 3-part: 66:41:0204016 (quarter)
|
||
* 4-part: 66:41:0204016:10 (parcel)
|
||
*/
|
||
const CAD_REGEX = /^\d+:\d+:\d+(?::\d+)?$/;
|
||
|
||
export function isValidCad(cad: string): boolean {
|
||
return CAD_REGEX.test(cad.trim());
|
||
}
|
||
|
||
export function getShortlist(): string[] {
|
||
if (typeof window === "undefined") return [];
|
||
try {
|
||
const raw = localStorage.getItem(STORAGE_KEY);
|
||
if (!raw) return [];
|
||
const parsed: unknown = JSON.parse(raw);
|
||
if (!Array.isArray(parsed)) return [];
|
||
// Narrow to a clean, de-duplicated, capped string[] — defends against a
|
||
// hand-edited / stale localStorage payload.
|
||
const seen = new Set<string>();
|
||
const out: string[] = [];
|
||
for (const item of parsed) {
|
||
if (typeof item !== "string") continue;
|
||
const cad = item.trim();
|
||
if (!isValidCad(cad) || seen.has(cad)) continue;
|
||
seen.add(cad);
|
||
out.push(cad);
|
||
if (out.length >= COMPARE_MAX) break;
|
||
}
|
||
return out;
|
||
} catch {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export function saveShortlist(cads: string[]): void {
|
||
if (typeof window === "undefined") return;
|
||
try {
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(cads.slice(0, COMPARE_MAX)));
|
||
} catch {
|
||
// ignore quota / disabled-storage errors — shortlist is best-effort
|
||
}
|
||
}
|