fix(ancpi): UAT debounce + list tooltips + expired download + ePay retry

1. UAT search: 150ms debounce prevents slow re-renders on keystroke
2. Lista mea tooltips: "Scoate Extrase CF" shows exact credit cost,
   status badges show expiry dates and clear instructions
3. Expired extracts: both Descarcă (old version) + Actualizează shown
4. ePay auto-connect: retry 2x with 3s delay, check session before
   connect, re-attempt on disconnect detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 12:14:34 +02:00
parent 5a6ab36aa7
commit 62777e9778
3 changed files with 216 additions and 92 deletions
@@ -621,27 +621,30 @@ export function ParcelSyncModule() {
setUatResults([]);
return;
}
const isDigit = /^\d+$/.test(raw);
const query = normalizeText(raw);
// Filter and sort: UAT name matches first, then county-only matches
const nameMatches: typeof uatData = [];
const countyOnlyMatches: typeof uatData = [];
const timer = setTimeout(() => {
const isDigit = /^\d+$/.test(raw);
const query = normalizeText(raw);
// Filter and sort: UAT name matches first, then county-only matches
const nameMatches: typeof uatData = [];
const countyOnlyMatches: typeof uatData = [];
for (const item of uatData) {
if (isDigit) {
if (item.siruta.startsWith(raw)) nameMatches.push(item);
} else {
const nameMatch = normalizeText(item.name).includes(query);
const countyMatch =
item.county && normalizeText(item.county).includes(query);
if (nameMatch) nameMatches.push(item);
else if (countyMatch) countyOnlyMatches.push(item);
for (const item of uatData) {
if (isDigit) {
if (item.siruta.startsWith(raw)) nameMatches.push(item);
} else {
const nameMatch = normalizeText(item.name).includes(query);
const countyMatch =
item.county && normalizeText(item.county).includes(query);
if (nameMatch) nameMatches.push(item);
else if (countyMatch) countyOnlyMatches.push(item);
}
}
}
// UAT name matches first (priority), then county-only matches
const results = [...nameMatches, ...countyOnlyMatches].slice(0, 12);
setUatResults(results);
// UAT name matches first (priority), then county-only matches
const results = [...nameMatches, ...countyOnlyMatches].slice(0, 12);
setUatResults(results);
}, 150);
return () => clearTimeout(timer);
}, [uatQuery, uatData]);
/* ════════════════════════════════════════════════════════════ */
@@ -2925,37 +2928,65 @@ export function ParcelSyncModule() {
CSV din lista
</Button>
{/* Download all valid CF extracts as ZIP */}
{searchList.some((p) => cfStatusMap[p.nrCad] === "valid") && (
<Button
size="sm"
variant="outline"
className="border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400"
disabled={listCfDownloading}
onClick={() => void handleListCfDownloadZip()}
>
{listCfDownloading ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
) : (
<Archive className="mr-1 h-3.5 w-3.5" />
)}
Descarca Extrase CF
</Button>
)}
{searchList.some((p) => cfStatusMap[p.nrCad] === "valid") && (() => {
const validCount = searchList.filter((p) => cfStatusMap[p.nrCad] === "valid").length;
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="outline"
className="border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400"
disabled={listCfDownloading}
onClick={() => void handleListCfDownloadZip()}
>
{listCfDownloading ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
) : (
<Archive className="mr-1 h-3.5 w-3.5" />
)}
Descarca Extrase CF
</Button>
</TooltipTrigger>
<TooltipContent>{`Descarca ZIP cu ${validCount} extrase valide din lista`}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})()}
{/* Order CF extracts for list */}
{epayStatus.connected && (
<Button
size="sm"
disabled={listCfOrdering}
onClick={() => void handleListCfOrder()}
>
{listCfOrdering ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
) : (
<FileText className="mr-1 h-3.5 w-3.5" />
)}
Scoate Extrase CF
</Button>
)}
{epayStatus.connected && (() => {
const newCount = searchList.filter((p) => {
const s = cfStatusMap[p.nrCad];
return s !== "valid" && s !== "expired" && s !== "processing";
}).length;
const updateCount = searchList.filter((p) => cfStatusMap[p.nrCad] === "expired").length;
const totalCredits = newCount + updateCount;
const validCount = searchList.filter((p) => cfStatusMap[p.nrCad] === "valid").length;
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
disabled={listCfOrdering}
onClick={() => void handleListCfOrder()}
>
{listCfOrdering ? (
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
) : (
<FileText className="mr-1 h-3.5 w-3.5" />
)}
Scoate Extrase CF
</Button>
</TooltipTrigger>
<TooltipContent>
{`Comanda ${newCount} extrase noi + ${updateCount} actualizari = ${totalCredits} credite. ${validCount} existente valide raman.`}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})()}
</div>
</div>
@@ -2999,6 +3030,7 @@ export function ParcelSyncModule() {
<tbody>
{searchList.map((p, idx) => {
const cfStatus = cfStatusMap[p.nrCad];
const cfExpiry = cfExpiryDates[p.nrCad];
return (
<tr
key={`list-${p.nrCad}-${p.immovablePk}`}
@@ -3022,23 +3054,38 @@ export function ParcelSyncModule() {
{p.proprietari || "\u2014"}
</td>
<td className="px-3 py-2 text-center">
{cfStatus === "valid" ? (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-400 dark:border-emerald-800">
Valid
</span>
) : cfStatus === "expired" ? (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-950/40 dark:text-orange-400 dark:border-orange-800">
Expirat
</span>
) : cfStatus === "processing" ? (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-yellow-100 text-yellow-700 border-yellow-200 dark:bg-yellow-950/40 dark:text-yellow-400 dark:border-yellow-800 animate-pulse">
Procesare
</span>
) : (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-muted text-muted-foreground border-muted-foreground/20">
Lipsa
</span>
)}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
{cfStatus === "valid" ? (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-400 dark:border-emerald-800 cursor-default">
Valid
</span>
) : cfStatus === "expired" ? (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-950/40 dark:text-orange-400 dark:border-orange-800 cursor-default">
Expirat
</span>
) : cfStatus === "processing" ? (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-yellow-100 text-yellow-700 border-yellow-200 dark:bg-yellow-950/40 dark:text-yellow-400 dark:border-yellow-800 animate-pulse cursor-default">
Procesare
</span>
) : (
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium bg-muted text-muted-foreground border-muted-foreground/20 cursor-default">
Lipsa
</span>
)}
</TooltipTrigger>
<TooltipContent>
{cfStatus === "valid"
? (cfExpiry ? `Extras CF valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid")
: cfStatus === "expired"
? (cfExpiry ? `Extras CF expirat pe ${formatShortDate(cfExpiry)}. Va fi actualizat automat la 'Scoate Extrase CF'.` : "Extras CF expirat. Va fi actualizat automat la 'Scoate Extrase CF'.")
: cfStatus === "processing"
? "Comanda in curs de procesare"
: "Nu exista extras CF. Apasa 'Scoate Extrase CF' pentru a comanda."}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</td>
<td className="px-3 py-2">
<Button