feat(ancpi): re-download with CF matching + tooltips + animations

Re-download: all 7 orders re-downloaded using documentsByCadastral
for correct CF→document matching. No more hardcoded order→parcel map.

Tooltips on all CF extract UI elements:
- No extract: "Comandă extras CF (1 credit)"
- Valid: "Valid până la DD.MM.YYYY" + "Descarcă extras CF"
- Expired: "Expirat pe DD.MM.YYYY" + "Comandă extras CF nou (1 credit)"
- Processing: "Comanda în curs de procesare"

Animations: Loader2 spinner while ordering, transition to green check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 09:38:23 +02:00
parent 0c94af75d3
commit a826f45b24
3 changed files with 457 additions and 263 deletions
@@ -1,8 +1,14 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { FileText, Loader2, Check } from "lucide-react";
import { FileText, Loader2, Check, RefreshCw } from "lucide-react";
import { Button } from "@/shared/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/shared/components/ui/tooltip";
import { cn } from "@/shared/lib/utils";
import type { EpaySessionStatus } from "./epay-connect";
@@ -15,6 +21,10 @@ type Props = {
siruta: string;
judetName: string;
uatName: string;
/** Custom label for the button (e.g. "Actualizeaza" for expired extracts) */
label?: string;
/** Custom tooltip text */
tooltipText?: string;
};
/* ------------------------------------------------------------------ */
@@ -26,6 +36,8 @@ export function EpayOrderButton({
siruta,
judetName,
uatName,
label,
tooltipText,
}: Props) {
const [ordering, setOrdering] = useState(false);
const [ordered, setOrdered] = useState(false);
@@ -93,12 +105,12 @@ export function EpayOrderButton({
});
const data = (await res.json()) as { orders?: unknown[]; error?: string };
if (!res.ok || data.error) {
if (mountedRef.current) setError(data.error ?? "Eroare comandă");
if (mountedRef.current) setError(data.error ?? "Eroare comanda");
} else {
if (mountedRef.current) setOrdered(true);
}
} catch {
if (mountedRef.current) setError("Eroare rețea");
if (mountedRef.current) setError("Eroare retea");
} finally {
if (mountedRef.current) setOrdering(false);
}
@@ -109,45 +121,95 @@ export function EpayOrderButton({
(epayStatus.credits != null && epayStatus.credits < 1) ||
ordering;
const isRenew = !!label; // "Actualizeaza" mode for expired extracts
const Icon = isRenew ? RefreshCw : FileText;
const resolveTooltip = (): string => {
if (error) return error;
if (ordering) return "Se comanda...";
if (ordered) return "Extras CF valid";
if (!epayStatus.connected) return "ePay neconectat";
if (epayStatus.credits != null && epayStatus.credits < 1) return "Credite insuficiente";
return tooltipText ?? "Comanda extras CF (1 credit)";
};
if (ordered) {
return (
<Button
size="sm"
variant="ghost"
className="h-7 w-7 p-0 text-emerald-600 dark:text-emerald-400"
title="Extras CF comandat"
disabled
>
<Check className="h-3.5 w-3.5" />
</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-7 gap-1 px-1.5 text-emerald-600 dark:text-emerald-400"
disabled
>
<Check className="h-3.5 w-3.5" />
<span className="text-[10px]">Extras CF valid</span>
</Button>
</TooltipTrigger>
<TooltipContent>Extras CF comandat cu succes</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
if (label) {
// Render as a compact text button with label (for "Actualizeaza" etc.)
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
className={cn(
"h-7 gap-1 px-1.5 text-[10px]",
error && "text-destructive",
ordering && "text-muted-foreground",
)}
disabled={disabled}
onClick={() => void handleOrder()}
>
{ordering ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Icon className="h-3 w-3" />
)}
{ordering ? "Se comanda..." : label}
</Button>
</TooltipTrigger>
<TooltipContent>{resolveTooltip()}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
// Default: icon-only button
return (
<Button
size="sm"
variant="ghost"
className={cn(
"h-7 w-7 p-0",
error && "text-destructive",
)}
title={
error
? error
: !epayStatus.connected
? "ePay neconectat"
: epayStatus.credits != null && epayStatus.credits < 1
? "Credite insuficiente"
: "Extras CF"
}
disabled={disabled}
onClick={() => void handleOrder()}
>
{ordering ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<FileText className="h-3.5 w-3.5" />
)}
</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
className={cn(
"h-7 w-7 p-0",
error && "text-destructive",
ordering && "text-muted-foreground",
)}
disabled={disabled}
onClick={() => void handleOrder()}
>
{ordering ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<FileText className="h-3.5 w-3.5" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>{resolveTooltip()}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
@@ -46,6 +46,12 @@ import {
DropdownMenuTrigger,
} from "@/shared/components/ui/dropdown-menu";
import { cn } from "@/shared/lib/utils";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/shared/components/ui/tooltip";
import {
LAYER_CATALOG,
LAYER_CATEGORY_LABELS,
@@ -126,6 +132,15 @@ function formatArea(val?: number | null) {
return val.toLocaleString("ro-RO", { maximumFractionDigits: 2 }) + " mp";
}
/** Format ISO date as DD.MM.YYYY (no time) */
function formatShortDate(iso?: string | null) {
if (!iso) return "—";
const d = new Date(iso);
const dd = String(d.getDate()).padStart(2, "0");
const mm = String(d.getMonth() + 1).padStart(2, "0");
return `${dd}.${mm}.${d.getFullYear()}`;
}
/* ------------------------------------------------------------------ */
/* Connection Status Pill */
/* ------------------------------------------------------------------ */
@@ -447,6 +462,8 @@ export function ParcelSyncModule() {
const [cfStatusMap, setCfStatusMap] = useState<Record<string, string>>({});
/** Latest completed extract IDs per nrCadastral */
const [cfLatestIds, setCfLatestIds] = useState<Record<string, string>>({});
/** Expiry dates per nrCadastral (ISO string) */
const [cfExpiryDates, setCfExpiryDates] = useState<Record<string, string>>({});
/** Whether we're currently loading CF statuses */
const [cfStatusLoading, setCfStatusLoading] = useState(false);
/** List CF batch order state */
@@ -1534,12 +1551,18 @@ export function ParcelSyncModule() {
}
if (data.latestById) {
const idMap: Record<string, string> = {};
const expiryMap: Record<string, string> = {};
for (const [nr, rec] of Object.entries(data.latestById)) {
if (rec && typeof rec === "object" && "id" in rec) {
idMap[nr] = (rec as { id: string }).id;
const expires = (rec as { expiresAt: string | null }).expiresAt;
if (expires) {
expiryMap[nr] = expires;
}
}
}
setCfLatestIds((prev) => ({ ...prev, ...idMap }));
setCfExpiryDates((prev) => ({ ...prev, ...expiryMap }));
}
} catch {
/* silent */
@@ -2318,61 +2341,93 @@ export function ParcelSyncModule() {
{p.immovablePk && sirutaValid && (() => {
const cfStatus = cfStatusMap[p.nrCad];
const extractId = cfLatestIds[p.nrCad];
const cfExpiry = cfExpiryDates[p.nrCad];
if (cfStatus === "valid") {
return (
<div className="flex items-center gap-1">
<Badge
variant="outline"
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400"
>
Extras CF valid
</Badge>
{extractId && (
<Button
size="sm"
variant="ghost"
className="h-7 w-7 p-0 text-emerald-600"
title="Descarca extras CF"
asChild
>
<a
href={`/api/ancpi/download?id=${extractId}`}
target="_blank"
rel="noopener noreferrer"
>
<Download className="h-3.5 w-3.5" />
</a>
</Button>
)}
</div>
<TooltipProvider>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400 cursor-default"
>
Extras CF
</Badge>
</TooltipTrigger>
<TooltipContent>
{cfExpiry ? `Valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid"}
</TooltipContent>
</Tooltip>
{extractId && (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-7 w-7 p-0 text-emerald-600"
asChild
>
<a
href={`/api/ancpi/download?id=${extractId}`}
target="_blank"
rel="noopener noreferrer"
>
<Download className="h-3.5 w-3.5" />
</a>
</Button>
</TooltipTrigger>
<TooltipContent>Descarca extras CF</TooltipContent>
</Tooltip>
)}
</div>
</TooltipProvider>
);
}
if (cfStatus === "expired") {
return (
<div className="flex items-center gap-1">
<Badge
variant="outline"
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400"
>
Extras CF expirat
</Badge>
<EpayOrderButton
nrCadastral={p.nrCad}
siruta={siruta}
judetName={selectedUat?.county ?? ""}
uatName={selectedUat?.name ?? ""}
/>
</div>
<TooltipProvider>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400 cursor-default"
>
Expirat
</Badge>
</TooltipTrigger>
<TooltipContent>
{cfExpiry ? `Expirat pe ${formatShortDate(cfExpiry)}` : "Extras CF expirat"}
</TooltipContent>
</Tooltip>
<EpayOrderButton
nrCadastral={p.nrCad}
siruta={siruta}
judetName={selectedUat?.county ?? ""}
uatName={selectedUat?.name ?? ""}
label="Actualizeaza"
tooltipText="Comanda extras CF nou (1 credit)"
/>
</div>
</TooltipProvider>
);
}
if (cfStatus === "processing") {
return (
<Badge
variant="outline"
className="text-[10px] border-yellow-200 text-yellow-600 dark:border-yellow-800 dark:text-yellow-400 animate-pulse"
>
Se proceseaza...
</Badge>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="text-[10px] border-yellow-200 text-yellow-600 dark:border-yellow-800 dark:text-yellow-400 animate-pulse cursor-default"
>
Se proceseaza...
</Badge>
</TooltipTrigger>
<TooltipContent>Comanda in curs de procesare</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
// "none" or unknown
@@ -2382,6 +2437,7 @@ export function ParcelSyncModule() {
siruta={siruta}
judetName={selectedUat?.county ?? ""}
uatName={selectedUat?.name ?? ""}
tooltipText="Comanda extras CF (1 credit)"
/>
);
})()}
@@ -2639,61 +2695,93 @@ export function ParcelSyncModule() {
{r.immovablePk && sirutaValid && (() => {
const cfStatus = cfStatusMap[r.nrCad];
const extractId = cfLatestIds[r.nrCad];
const cfExpiry = cfExpiryDates[r.nrCad];
if (cfStatus === "valid") {
return (
<div className="flex items-center gap-1">
<Badge
variant="outline"
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400"
>
Extras CF valid
</Badge>
{extractId && (
<Button
size="sm"
variant="ghost"
className="h-7 w-7 p-0 text-emerald-600"
title="Descarca extras CF"
asChild
>
<a
href={`/api/ancpi/download?id=${extractId}`}
target="_blank"
rel="noopener noreferrer"
>
<Download className="h-3.5 w-3.5" />
</a>
</Button>
)}
</div>
<TooltipProvider>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400 cursor-default"
>
Extras CF
</Badge>
</TooltipTrigger>
<TooltipContent>
{cfExpiry ? `Valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid"}
</TooltipContent>
</Tooltip>
{extractId && (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-7 w-7 p-0 text-emerald-600"
asChild
>
<a
href={`/api/ancpi/download?id=${extractId}`}
target="_blank"
rel="noopener noreferrer"
>
<Download className="h-3.5 w-3.5" />
</a>
</Button>
</TooltipTrigger>
<TooltipContent>Descarca extras CF</TooltipContent>
</Tooltip>
)}
</div>
</TooltipProvider>
);
}
if (cfStatus === "expired") {
return (
<div className="flex items-center gap-1">
<Badge
variant="outline"
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400"
>
Extras CF expirat
</Badge>
<EpayOrderButton
nrCadastral={r.nrCad}
siruta={siruta}
judetName={selectedUat?.county ?? ""}
uatName={selectedUat?.name ?? ""}
/>
</div>
<TooltipProvider>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400 cursor-default"
>
Expirat
</Badge>
</TooltipTrigger>
<TooltipContent>
{cfExpiry ? `Expirat pe ${formatShortDate(cfExpiry)}` : "Extras CF expirat"}
</TooltipContent>
</Tooltip>
<EpayOrderButton
nrCadastral={r.nrCad}
siruta={siruta}
judetName={selectedUat?.county ?? ""}
uatName={selectedUat?.name ?? ""}
label="Actualizeaza"
tooltipText="Comanda extras CF nou (1 credit)"
/>
</div>
</TooltipProvider>
);
}
if (cfStatus === "processing") {
return (
<Badge
variant="outline"
className="text-[10px] border-yellow-200 text-yellow-600 dark:border-yellow-800 dark:text-yellow-400 animate-pulse"
>
Se proceseaza...
</Badge>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="text-[10px] border-yellow-200 text-yellow-600 dark:border-yellow-800 dark:text-yellow-400 animate-pulse cursor-default"
>
Se proceseaza...
</Badge>
</TooltipTrigger>
<TooltipContent>Comanda in curs de procesare</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
return (
@@ -2702,6 +2790,7 @@ export function ParcelSyncModule() {
siruta={siruta}
judetName={selectedUat?.county ?? ""}
uatName={selectedUat?.name ?? ""}
tooltipText="Comanda extras CF (1 credit)"
/>
);
})()}