feat(ancpi): selectable extracts with numbered ZIP download

- Checkbox on each row (ordered selection → numbered files in ZIP)
- "Descarcă selecție (N)" button appears when items selected
- Tooltip shows position in ZIP: "#1 in ZIP", "#2 in ZIP"
- Select-all checkbox in header
- Tooltips on Descarcă tot + Descarcă selecție buttons

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 10:37:03 +02:00
parent 87281bc690
commit 5a6ab36aa7
+129 -14
View File
@@ -179,6 +179,30 @@ export function EpayTab() {
/* -- Filter ------------------------------------------------------ */
const [filterTab, setFilterTab] = useState<FilterValue>("all");
/* -- Selection (ordered — index = numbering in ZIP) -------------- */
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [downloadingSelection, setDownloadingSelection] = useState(false);
const toggleSelect = (id: string) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
);
};
const handleDownloadSelection = async () => {
if (selectedIds.length === 0) return;
setDownloadingSelection(true);
try {
const ids = selectedIds.join(",");
const a = document.createElement("a");
a.href = `/api/ancpi/download-zip?ids=${encodeURIComponent(ids)}`;
a.download = `Extrase_CF_selectie_${selectedIds.length}.zip`;
a.click();
} finally {
setTimeout(() => setDownloadingSelection(false), 2000);
}
};
/* -- Search ------------------------------------------------------ */
const [searchQuery, setSearchQuery] = useState("");
@@ -398,22 +422,57 @@ export function EpayTab() {
</span>
</div>
<div className="flex items-center gap-2">
{/* Download selection */}
{selectedIds.length > 0 && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
size="sm"
className="h-7 text-xs"
disabled={downloadingSelection}
onClick={() => void handleDownloadSelection()}
>
{downloadingSelection ? (
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
) : (
<Download className="h-3 w-3 mr-1" />
)}
Descarca selectie ({selectedIds.length})
</Button>
</TooltipTrigger>
<TooltipContent>
ZIP cu {selectedIds.length} extrase numerotate in ordinea selectarii
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{/* Download all valid */}
{validCount > 0 && (
<Button
variant="outline"
size="sm"
className="h-7 text-xs"
disabled={downloadingAll}
onClick={() => void handleDownloadAll()}
>
{downloadingAll ? (
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
) : (
<Archive className="h-3 w-3 mr-1" />
)}
Descarca tot ({validCount})
</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-7 text-xs"
disabled={downloadingAll}
onClick={() => void handleDownloadAll()}
>
{downloadingAll ? (
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
) : (
<Archive className="h-3 w-3 mr-1" />
)}
Descarca tot ({validCount})
</Button>
</TooltipTrigger>
<TooltipContent>
ZIP cu toate extrasele valabile
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<Button
variant="ghost"
@@ -496,6 +555,38 @@ export function EpayTab() {
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/40">
<th className="px-3 py-2 text-center font-medium w-8">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<input
type="checkbox"
className="rounded cursor-pointer"
checked={
filteredOrders.length > 0 &&
filteredOrders
.filter((o) => o.status === "completed" && o.minioPath)
.every((o) => selectedIds.includes(o.id))
}
onChange={() => {
const downloadable = filteredOrders.filter(
(o) => o.status === "completed" && o.minioPath,
);
const allSelected = downloadable.every((o) =>
selectedIds.includes(o.id),
);
if (allSelected) {
setSelectedIds([]);
} else {
setSelectedIds(downloadable.map((o) => o.id));
}
}}
/>
</TooltipTrigger>
<TooltipContent>Selecteaza/deselecteaza tot</TooltipContent>
</Tooltip>
</TooltipProvider>
</th>
<th className="px-3 py-2 text-left font-medium w-8">#</th>
<th className="px-3 py-2 text-left font-medium">
Nr. Cadastral
@@ -523,6 +614,30 @@ export function EpayTab() {
expired && "opacity-70",
)}
>
{/* Checkbox */}
<td className="px-3 py-2 text-center">
{order.status === "completed" && order.minioPath ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<input
type="checkbox"
className="rounded cursor-pointer"
checked={selectedIds.includes(order.id)}
onChange={() => toggleSelect(order.id)}
/>
</TooltipTrigger>
<TooltipContent>
{selectedIds.includes(order.id)
? `#${selectedIds.indexOf(order.id) + 1} in ZIP`
: "Adauga in selectie"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<span className="text-muted-foreground/30"></span>
)}
</td>
{/* # */}
<td className="px-3 py-2 text-xs text-muted-foreground tabular-nums">
{idx + 1}