gendesign/frontend/src/lib/compare-shortlist.ts
Light1YT adc6dde015
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m59s
Deploy / deploy (push) Successful in 1m7s
feat(site-finder): multi-cad сравнение (#50) + analytics поиск/cross-links (#65)
#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
2026-06-13 17:14:15 +00:00

60 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Site Finder multi-cad compare — localStorage-backed shortlist (issue #50).
*
* The compare page lets a developer pin 25 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
}
}