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:
+179
-136
@@ -86,46 +86,25 @@ export async function GET(req: Request) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── download ── Download PDFs from 5 existing orders
|
// ── download ── Re-download PDFs from all known ePay orders
|
||||||
if (step === "download") {
|
if (step === "download") {
|
||||||
const client = await EpayClient.create(username, password);
|
const client = await EpayClient.create(username, password);
|
||||||
createEpaySession(username, password, await client.getCredits());
|
createEpaySession(username, password, await client.getCredits());
|
||||||
|
|
||||||
// Known orders from previous test
|
// All known order IDs (MinIO + DB were cleaned, need re-download)
|
||||||
const orderIds = ["9685480", "9685481", "9685482", "9685483", "9685484"];
|
// Single orders: mapping unknown — use documentsByCadastral to discover CF
|
||||||
|
const singleOrderIds = ["9685480", "9685481", "9685482", "9685483", "9685484"];
|
||||||
|
// Batch orders: documentsByCadastral maps CF -> doc correctly
|
||||||
|
const batchOrderIds = ["9685487", "9685488"];
|
||||||
|
const allOrderIds = [...singleOrderIds, ...batchOrderIds];
|
||||||
|
|
||||||
// Mapping: orderId → nrCadastral (5 orders for 3 parcels)
|
// UAT name lookup for DB records
|
||||||
// Orders were for: 345295 (Cluj-Napoca), 63565 (Feleacu), 88089 (Florești)
|
const uatLookup: Record<string, { uatId: number; uatName: string }> = {
|
||||||
// 5 orders for 3 parcels = some duplicates
|
"345295": { uatId: 54975, uatName: "Cluj-Napoca" },
|
||||||
const orderParcelMap: Record<
|
"63565": { uatId: 57582, uatName: "Feleacu" },
|
||||||
string,
|
"88089": { uatId: 57706, uatName: "Floresti" },
|
||||||
{ nrCadastral: string; judetName: string; uatName: string }
|
"61904": { uatId: 57582, uatName: "Feleacu" },
|
||||||
> = {
|
"309952": { uatId: 54975, uatName: "Cluj-Napoca" },
|
||||||
"9685480": {
|
|
||||||
nrCadastral: "345295",
|
|
||||||
judetName: "CLUJ",
|
|
||||||
uatName: "Cluj-Napoca",
|
|
||||||
},
|
|
||||||
"9685481": {
|
|
||||||
nrCadastral: "63565",
|
|
||||||
judetName: "CLUJ",
|
|
||||||
uatName: "Feleacu",
|
|
||||||
},
|
|
||||||
"9685482": {
|
|
||||||
nrCadastral: "88089",
|
|
||||||
judetName: "CLUJ",
|
|
||||||
uatName: "Florești",
|
|
||||||
},
|
|
||||||
"9685483": {
|
|
||||||
nrCadastral: "345295",
|
|
||||||
judetName: "CLUJ",
|
|
||||||
uatName: "Cluj-Napoca",
|
|
||||||
},
|
|
||||||
"9685484": {
|
|
||||||
nrCadastral: "63565",
|
|
||||||
judetName: "CLUJ",
|
|
||||||
uatName: "Feleacu",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const results: Array<{
|
const results: Array<{
|
||||||
@@ -138,31 +117,18 @@ export async function GET(req: Request) {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
for (const orderId of orderIds) {
|
for (const orderId of allOrderIds) {
|
||||||
const parcelInfo = orderParcelMap[orderId];
|
|
||||||
if (!parcelInfo) {
|
|
||||||
results.push({
|
|
||||||
orderId,
|
|
||||||
nrCadastral: "unknown",
|
|
||||||
status: "error",
|
|
||||||
documents: 0,
|
|
||||||
downloaded: false,
|
|
||||||
error: "No parcel mapping for orderId",
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get order status and document info
|
// Get order status — documentsByCadastral maps CF → doc
|
||||||
const orderStatus = await client.getOrderStatus(orderId);
|
const orderStatus = await client.getOrderStatus(orderId);
|
||||||
console.log(
|
console.log(
|
||||||
`[ancpi-test] Order ${orderId}: status=${orderStatus.status}, docs=${orderStatus.documents.length}`,
|
`[ancpi-test] Order ${orderId}: status=${orderStatus.status}, docs=${orderStatus.documents.length}, byCF=${orderStatus.documentsByCadastral.size}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (orderStatus.documents.length === 0) {
|
if (orderStatus.documents.length === 0) {
|
||||||
results.push({
|
results.push({
|
||||||
orderId,
|
orderId,
|
||||||
nrCadastral: parcelInfo.nrCadastral,
|
nrCadastral: "unknown",
|
||||||
status: orderStatus.status,
|
status: orderStatus.status,
|
||||||
documents: 0,
|
documents: 0,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
@@ -171,94 +137,171 @@ export async function GET(req: Request) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find downloadable PDF document
|
// Process each document by cadastral number from the map
|
||||||
const doc = orderStatus.documents.find(
|
if (orderStatus.documentsByCadastral.size > 0) {
|
||||||
(d) => d.downloadValabil && d.contentType === "application/pdf",
|
for (const [cfNumber, doc] of orderStatus.documentsByCadastral) {
|
||||||
);
|
if (!doc.downloadValabil || doc.contentType !== "application/pdf") {
|
||||||
|
results.push({
|
||||||
|
orderId,
|
||||||
|
nrCadastral: cfNumber,
|
||||||
|
status: orderStatus.status,
|
||||||
|
documents: orderStatus.documents.length,
|
||||||
|
downloaded: false,
|
||||||
|
error: "Document not downloadable or not PDF",
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!doc) {
|
try {
|
||||||
results.push({
|
// Download the PDF
|
||||||
orderId,
|
const pdfBuffer = await client.downloadDocument(doc.idDocument, 4);
|
||||||
nrCadastral: parcelInfo.nrCadastral,
|
console.log(
|
||||||
status: orderStatus.status,
|
`[ancpi-test] Downloaded doc ${doc.idDocument} (CF ${cfNumber}): ${pdfBuffer.length} bytes`,
|
||||||
documents: orderStatus.documents.length,
|
);
|
||||||
downloaded: false,
|
|
||||||
error: "No downloadable PDF found",
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the PDF
|
// Resolve UAT info for this cadastral number
|
||||||
const pdfBuffer = await client.downloadDocument(doc.idDocument, 4);
|
const uat = uatLookup[cfNumber];
|
||||||
console.log(
|
const uatId = uat?.uatId ?? 0;
|
||||||
`[ancpi-test] Downloaded doc ${doc.idDocument}: ${pdfBuffer.length} bytes`,
|
const uatName = uat?.uatName ?? "Necunoscut";
|
||||||
);
|
|
||||||
|
|
||||||
// Store in MinIO
|
// Store in MinIO
|
||||||
const { path, index } = await storeCfExtract(
|
const { path, index } = await storeCfExtract(
|
||||||
pdfBuffer,
|
pdfBuffer,
|
||||||
parcelInfo.nrCadastral,
|
cfNumber,
|
||||||
{
|
{
|
||||||
"ancpi-order-id": orderId,
|
"ancpi-order-id": orderId,
|
||||||
"nr-cadastral": parcelInfo.nrCadastral,
|
"nr-cadastral": cfNumber,
|
||||||
judet: parcelInfo.judetName,
|
judet: "CLUJ",
|
||||||
uat: parcelInfo.uatName,
|
uat: uatName,
|
||||||
"data-document": doc.dataDocument ?? "",
|
"data-document": doc.dataDocument ?? "",
|
||||||
stare: orderStatus.status,
|
stare: orderStatus.status,
|
||||||
produs: "EXI_ONLINE",
|
produs: "EXI_ONLINE",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Upsert CfExtract record — find by orderId or create
|
// Calculate dates
|
||||||
const documentDate = doc.dataDocument
|
const documentDate = doc.dataDocument
|
||||||
? new Date(doc.dataDocument)
|
? new Date(doc.dataDocument)
|
||||||
: new Date();
|
: new Date();
|
||||||
const expiresAt = new Date(documentDate);
|
const expiresAt = new Date(documentDate);
|
||||||
expiresAt.setDate(expiresAt.getDate() + 30);
|
expiresAt.setDate(expiresAt.getDate() + 30);
|
||||||
|
|
||||||
// Try to find existing record by orderId
|
// Always create new records (DB was cleaned)
|
||||||
const existing = await prisma.cfExtract.findFirst({
|
// Increment version for duplicate parcels
|
||||||
where: { orderId },
|
const maxVersion = await prisma.cfExtract.aggregate({
|
||||||
});
|
where: { nrCadastral: cfNumber },
|
||||||
|
_max: { version: true },
|
||||||
|
});
|
||||||
|
|
||||||
if (existing) {
|
await prisma.cfExtract.create({
|
||||||
// Update existing record
|
data: {
|
||||||
await prisma.cfExtract.update({
|
orderId,
|
||||||
where: { id: existing.id },
|
nrCadastral: cfNumber,
|
||||||
data: {
|
nrCF: cfNumber,
|
||||||
status: "completed",
|
judetIndex: 127,
|
||||||
epayStatus: orderStatus.status,
|
judetName: "CLUJ",
|
||||||
idDocument: doc.idDocument,
|
uatId,
|
||||||
documentName: doc.nume,
|
uatName,
|
||||||
documentDate,
|
status: "completed",
|
||||||
minioPath: path,
|
epayStatus: orderStatus.status,
|
||||||
minioIndex: index,
|
idDocument: doc.idDocument,
|
||||||
completedAt: new Date(),
|
documentName: doc.nume,
|
||||||
expiresAt,
|
documentDate,
|
||||||
errorMessage: null,
|
minioPath: path,
|
||||||
},
|
minioIndex: index,
|
||||||
});
|
completedAt: new Date(),
|
||||||
|
expiresAt,
|
||||||
|
version: (maxVersion._max.version ?? 0) + 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
orderId,
|
||||||
|
nrCadastral: cfNumber,
|
||||||
|
status: orderStatus.status,
|
||||||
|
documents: orderStatus.documents.length,
|
||||||
|
downloaded: true,
|
||||||
|
minioPath: path,
|
||||||
|
});
|
||||||
|
} catch (dlErr) {
|
||||||
|
const msg = dlErr instanceof Error ? dlErr.message : String(dlErr);
|
||||||
|
console.error(
|
||||||
|
`[ancpi-test] Failed to download doc for CF ${cfNumber} in order ${orderId}:`,
|
||||||
|
msg,
|
||||||
|
);
|
||||||
|
results.push({
|
||||||
|
orderId,
|
||||||
|
nrCadastral: cfNumber,
|
||||||
|
status: "error",
|
||||||
|
documents: orderStatus.documents.length,
|
||||||
|
downloaded: false,
|
||||||
|
error: msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create new record
|
// Fallback: no CF mapping, process first downloadable document
|
||||||
|
const doc = orderStatus.documents.find(
|
||||||
|
(d) => d.downloadValabil && d.contentType === "application/pdf",
|
||||||
|
);
|
||||||
|
if (!doc) {
|
||||||
|
results.push({
|
||||||
|
orderId,
|
||||||
|
nrCadastral: "unknown",
|
||||||
|
status: orderStatus.status,
|
||||||
|
documents: orderStatus.documents.length,
|
||||||
|
downloaded: false,
|
||||||
|
error: "No downloadable PDF and no CF mapping found",
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to extract CF from document name (e.g. "Extras_Informare_345295.pdf")
|
||||||
|
const cfFromName = doc.nume.match(/(\d{4,})/)?.[1] ?? "unknown";
|
||||||
|
|
||||||
|
const pdfBuffer = await client.downloadDocument(doc.idDocument, 4);
|
||||||
|
console.log(
|
||||||
|
`[ancpi-test] Downloaded doc ${doc.idDocument} (CF from name: ${cfFromName}): ${pdfBuffer.length} bytes`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const uat = uatLookup[cfFromName];
|
||||||
|
const uatId = uat?.uatId ?? 0;
|
||||||
|
const uatName = uat?.uatName ?? "Necunoscut";
|
||||||
|
|
||||||
|
const { path, index } = await storeCfExtract(
|
||||||
|
pdfBuffer,
|
||||||
|
cfFromName,
|
||||||
|
{
|
||||||
|
"ancpi-order-id": orderId,
|
||||||
|
"nr-cadastral": cfFromName,
|
||||||
|
judet: "CLUJ",
|
||||||
|
uat: uatName,
|
||||||
|
"data-document": doc.dataDocument ?? "",
|
||||||
|
stare: orderStatus.status,
|
||||||
|
produs: "EXI_ONLINE",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const documentDate = doc.dataDocument
|
||||||
|
? new Date(doc.dataDocument)
|
||||||
|
: new Date();
|
||||||
|
const expiresAt = new Date(documentDate);
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 30);
|
||||||
|
|
||||||
const maxVersion = await prisma.cfExtract.aggregate({
|
const maxVersion = await prisma.cfExtract.aggregate({
|
||||||
where: { nrCadastral: parcelInfo.nrCadastral },
|
where: { nrCadastral: cfFromName },
|
||||||
_max: { version: true },
|
_max: { version: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.cfExtract.create({
|
await prisma.cfExtract.create({
|
||||||
data: {
|
data: {
|
||||||
orderId,
|
orderId,
|
||||||
nrCadastral: parcelInfo.nrCadastral,
|
nrCadastral: cfFromName,
|
||||||
nrCF: parcelInfo.nrCadastral,
|
nrCF: cfFromName,
|
||||||
judetIndex: 127,
|
judetIndex: 127,
|
||||||
judetName: parcelInfo.judetName,
|
judetName: "CLUJ",
|
||||||
uatId:
|
uatId,
|
||||||
parcelInfo.uatName === "Cluj-Napoca"
|
uatName,
|
||||||
? 54975
|
|
||||||
: parcelInfo.uatName === "Feleacu"
|
|
||||||
? 57582
|
|
||||||
: 57706,
|
|
||||||
uatName: parcelInfo.uatName,
|
|
||||||
status: "completed",
|
status: "completed",
|
||||||
epayStatus: orderStatus.status,
|
epayStatus: orderStatus.status,
|
||||||
idDocument: doc.idDocument,
|
idDocument: doc.idDocument,
|
||||||
@@ -271,16 +314,16 @@ export async function GET(req: Request) {
|
|||||||
version: (maxVersion._max.version ?? 0) + 1,
|
version: (maxVersion._max.version ?? 0) + 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
orderId,
|
orderId,
|
||||||
nrCadastral: parcelInfo.nrCadastral,
|
nrCadastral: cfFromName,
|
||||||
status: orderStatus.status,
|
status: orderStatus.status,
|
||||||
documents: orderStatus.documents.length,
|
documents: orderStatus.documents.length,
|
||||||
downloaded: true,
|
downloaded: true,
|
||||||
minioPath: path,
|
minioPath: path,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : String(error);
|
error instanceof Error ? error.message : String(error);
|
||||||
@@ -290,7 +333,7 @@ export async function GET(req: Request) {
|
|||||||
);
|
);
|
||||||
results.push({
|
results.push({
|
||||||
orderId,
|
orderId,
|
||||||
nrCadastral: parcelInfo.nrCadastral,
|
nrCadastral: "unknown",
|
||||||
status: "error",
|
status: "error",
|
||||||
documents: 0,
|
documents: 0,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
@@ -301,7 +344,7 @@ export async function GET(req: Request) {
|
|||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
step: "download",
|
step: "download",
|
||||||
totalOrders: orderIds.length,
|
totalOrders: allOrderIds.length,
|
||||||
results,
|
results,
|
||||||
summary: {
|
summary: {
|
||||||
downloaded: results.filter((r) => r.downloaded).length,
|
downloaded: results.filter((r) => r.downloaded).length,
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from "react";
|
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 { Button } from "@/shared/components/ui/button";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/shared/components/ui/tooltip";
|
||||||
import { cn } from "@/shared/lib/utils";
|
import { cn } from "@/shared/lib/utils";
|
||||||
import type { EpaySessionStatus } from "./epay-connect";
|
import type { EpaySessionStatus } from "./epay-connect";
|
||||||
|
|
||||||
@@ -15,6 +21,10 @@ type Props = {
|
|||||||
siruta: string;
|
siruta: string;
|
||||||
judetName: string;
|
judetName: string;
|
||||||
uatName: 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,
|
siruta,
|
||||||
judetName,
|
judetName,
|
||||||
uatName,
|
uatName,
|
||||||
|
label,
|
||||||
|
tooltipText,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [ordering, setOrdering] = useState(false);
|
const [ordering, setOrdering] = useState(false);
|
||||||
const [ordered, setOrdered] = useState(false);
|
const [ordered, setOrdered] = useState(false);
|
||||||
@@ -93,12 +105,12 @@ export function EpayOrderButton({
|
|||||||
});
|
});
|
||||||
const data = (await res.json()) as { orders?: unknown[]; error?: string };
|
const data = (await res.json()) as { orders?: unknown[]; error?: string };
|
||||||
if (!res.ok || data.error) {
|
if (!res.ok || data.error) {
|
||||||
if (mountedRef.current) setError(data.error ?? "Eroare comandă");
|
if (mountedRef.current) setError(data.error ?? "Eroare comanda");
|
||||||
} else {
|
} else {
|
||||||
if (mountedRef.current) setOrdered(true);
|
if (mountedRef.current) setOrdered(true);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (mountedRef.current) setError("Eroare rețea");
|
if (mountedRef.current) setError("Eroare retea");
|
||||||
} finally {
|
} finally {
|
||||||
if (mountedRef.current) setOrdering(false);
|
if (mountedRef.current) setOrdering(false);
|
||||||
}
|
}
|
||||||
@@ -109,45 +121,95 @@ export function EpayOrderButton({
|
|||||||
(epayStatus.credits != null && epayStatus.credits < 1) ||
|
(epayStatus.credits != null && epayStatus.credits < 1) ||
|
||||||
ordering;
|
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) {
|
if (ordered) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<TooltipProvider>
|
||||||
size="sm"
|
<Tooltip>
|
||||||
variant="ghost"
|
<TooltipTrigger asChild>
|
||||||
className="h-7 w-7 p-0 text-emerald-600 dark:text-emerald-400"
|
<Button
|
||||||
title="Extras CF comandat"
|
size="sm"
|
||||||
disabled
|
variant="ghost"
|
||||||
>
|
className="h-7 gap-1 px-1.5 text-emerald-600 dark:text-emerald-400"
|
||||||
<Check className="h-3.5 w-3.5" />
|
disabled
|
||||||
</Button>
|
>
|
||||||
|
<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 (
|
return (
|
||||||
<Button
|
<TooltipProvider>
|
||||||
size="sm"
|
<Tooltip>
|
||||||
variant="ghost"
|
<TooltipTrigger asChild>
|
||||||
className={cn(
|
<Button
|
||||||
"h-7 w-7 p-0",
|
size="sm"
|
||||||
error && "text-destructive",
|
variant="ghost"
|
||||||
)}
|
className={cn(
|
||||||
title={
|
"h-7 w-7 p-0",
|
||||||
error
|
error && "text-destructive",
|
||||||
? error
|
ordering && "text-muted-foreground",
|
||||||
: !epayStatus.connected
|
)}
|
||||||
? "ePay neconectat"
|
disabled={disabled}
|
||||||
: epayStatus.credits != null && epayStatus.credits < 1
|
onClick={() => void handleOrder()}
|
||||||
? "Credite insuficiente"
|
>
|
||||||
: "Extras CF"
|
{ordering ? (
|
||||||
}
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
disabled={disabled}
|
) : (
|
||||||
onClick={() => void handleOrder()}
|
<FileText className="h-3.5 w-3.5" />
|
||||||
>
|
)}
|
||||||
{ordering ? (
|
</Button>
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
</TooltipTrigger>
|
||||||
) : (
|
<TooltipContent>{resolveTooltip()}</TooltipContent>
|
||||||
<FileText className="h-3.5 w-3.5" />
|
</Tooltip>
|
||||||
)}
|
</TooltipProvider>
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/shared/components/ui/dropdown-menu";
|
} from "@/shared/components/ui/dropdown-menu";
|
||||||
import { cn } from "@/shared/lib/utils";
|
import { cn } from "@/shared/lib/utils";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/shared/components/ui/tooltip";
|
||||||
import {
|
import {
|
||||||
LAYER_CATALOG,
|
LAYER_CATALOG,
|
||||||
LAYER_CATEGORY_LABELS,
|
LAYER_CATEGORY_LABELS,
|
||||||
@@ -126,6 +132,15 @@ function formatArea(val?: number | null) {
|
|||||||
return val.toLocaleString("ro-RO", { maximumFractionDigits: 2 }) + " mp";
|
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 */
|
/* Connection Status Pill */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -447,6 +462,8 @@ export function ParcelSyncModule() {
|
|||||||
const [cfStatusMap, setCfStatusMap] = useState<Record<string, string>>({});
|
const [cfStatusMap, setCfStatusMap] = useState<Record<string, string>>({});
|
||||||
/** Latest completed extract IDs per nrCadastral */
|
/** Latest completed extract IDs per nrCadastral */
|
||||||
const [cfLatestIds, setCfLatestIds] = useState<Record<string, string>>({});
|
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 */
|
/** Whether we're currently loading CF statuses */
|
||||||
const [cfStatusLoading, setCfStatusLoading] = useState(false);
|
const [cfStatusLoading, setCfStatusLoading] = useState(false);
|
||||||
/** List CF batch order state */
|
/** List CF batch order state */
|
||||||
@@ -1534,12 +1551,18 @@ export function ParcelSyncModule() {
|
|||||||
}
|
}
|
||||||
if (data.latestById) {
|
if (data.latestById) {
|
||||||
const idMap: Record<string, string> = {};
|
const idMap: Record<string, string> = {};
|
||||||
|
const expiryMap: Record<string, string> = {};
|
||||||
for (const [nr, rec] of Object.entries(data.latestById)) {
|
for (const [nr, rec] of Object.entries(data.latestById)) {
|
||||||
if (rec && typeof rec === "object" && "id" in rec) {
|
if (rec && typeof rec === "object" && "id" in rec) {
|
||||||
idMap[nr] = (rec as { id: string }).id;
|
idMap[nr] = (rec as { id: string }).id;
|
||||||
|
const expires = (rec as { expiresAt: string | null }).expiresAt;
|
||||||
|
if (expires) {
|
||||||
|
expiryMap[nr] = expires;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCfLatestIds((prev) => ({ ...prev, ...idMap }));
|
setCfLatestIds((prev) => ({ ...prev, ...idMap }));
|
||||||
|
setCfExpiryDates((prev) => ({ ...prev, ...expiryMap }));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* silent */
|
/* silent */
|
||||||
@@ -2318,61 +2341,93 @@ export function ParcelSyncModule() {
|
|||||||
{p.immovablePk && sirutaValid && (() => {
|
{p.immovablePk && sirutaValid && (() => {
|
||||||
const cfStatus = cfStatusMap[p.nrCad];
|
const cfStatus = cfStatusMap[p.nrCad];
|
||||||
const extractId = cfLatestIds[p.nrCad];
|
const extractId = cfLatestIds[p.nrCad];
|
||||||
|
const cfExpiry = cfExpiryDates[p.nrCad];
|
||||||
if (cfStatus === "valid") {
|
if (cfStatus === "valid") {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<TooltipProvider>
|
||||||
<Badge
|
<div className="flex items-center gap-1">
|
||||||
variant="outline"
|
<Tooltip>
|
||||||
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400"
|
<TooltipTrigger asChild>
|
||||||
>
|
<Badge
|
||||||
Extras CF valid
|
variant="outline"
|
||||||
</Badge>
|
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400 cursor-default"
|
||||||
{extractId && (
|
>
|
||||||
<Button
|
Extras CF
|
||||||
size="sm"
|
</Badge>
|
||||||
variant="ghost"
|
</TooltipTrigger>
|
||||||
className="h-7 w-7 p-0 text-emerald-600"
|
<TooltipContent>
|
||||||
title="Descarca extras CF"
|
{cfExpiry ? `Valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid"}
|
||||||
asChild
|
</TooltipContent>
|
||||||
>
|
</Tooltip>
|
||||||
<a
|
{extractId && (
|
||||||
href={`/api/ancpi/download?id=${extractId}`}
|
<Tooltip>
|
||||||
target="_blank"
|
<TooltipTrigger asChild>
|
||||||
rel="noopener noreferrer"
|
<Button
|
||||||
>
|
size="sm"
|
||||||
<Download className="h-3.5 w-3.5" />
|
variant="ghost"
|
||||||
</a>
|
className="h-7 w-7 p-0 text-emerald-600"
|
||||||
</Button>
|
asChild
|
||||||
)}
|
>
|
||||||
</div>
|
<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") {
|
if (cfStatus === "expired") {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<TooltipProvider>
|
||||||
<Badge
|
<div className="flex items-center gap-1">
|
||||||
variant="outline"
|
<Tooltip>
|
||||||
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400"
|
<TooltipTrigger asChild>
|
||||||
>
|
<Badge
|
||||||
Extras CF expirat
|
variant="outline"
|
||||||
</Badge>
|
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400 cursor-default"
|
||||||
<EpayOrderButton
|
>
|
||||||
nrCadastral={p.nrCad}
|
Expirat
|
||||||
siruta={siruta}
|
</Badge>
|
||||||
judetName={selectedUat?.county ?? ""}
|
</TooltipTrigger>
|
||||||
uatName={selectedUat?.name ?? ""}
|
<TooltipContent>
|
||||||
/>
|
{cfExpiry ? `Expirat pe ${formatShortDate(cfExpiry)}` : "Extras CF expirat"}
|
||||||
</div>
|
</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") {
|
if (cfStatus === "processing") {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<TooltipProvider>
|
||||||
variant="outline"
|
<Tooltip>
|
||||||
className="text-[10px] border-yellow-200 text-yellow-600 dark:border-yellow-800 dark:text-yellow-400 animate-pulse"
|
<TooltipTrigger asChild>
|
||||||
>
|
<Badge
|
||||||
Se proceseaza...
|
variant="outline"
|
||||||
</Badge>
|
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
|
// "none" or unknown
|
||||||
@@ -2382,6 +2437,7 @@ export function ParcelSyncModule() {
|
|||||||
siruta={siruta}
|
siruta={siruta}
|
||||||
judetName={selectedUat?.county ?? ""}
|
judetName={selectedUat?.county ?? ""}
|
||||||
uatName={selectedUat?.name ?? ""}
|
uatName={selectedUat?.name ?? ""}
|
||||||
|
tooltipText="Comanda extras CF (1 credit)"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
@@ -2639,61 +2695,93 @@ export function ParcelSyncModule() {
|
|||||||
{r.immovablePk && sirutaValid && (() => {
|
{r.immovablePk && sirutaValid && (() => {
|
||||||
const cfStatus = cfStatusMap[r.nrCad];
|
const cfStatus = cfStatusMap[r.nrCad];
|
||||||
const extractId = cfLatestIds[r.nrCad];
|
const extractId = cfLatestIds[r.nrCad];
|
||||||
|
const cfExpiry = cfExpiryDates[r.nrCad];
|
||||||
if (cfStatus === "valid") {
|
if (cfStatus === "valid") {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<TooltipProvider>
|
||||||
<Badge
|
<div className="flex items-center gap-1">
|
||||||
variant="outline"
|
<Tooltip>
|
||||||
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400"
|
<TooltipTrigger asChild>
|
||||||
>
|
<Badge
|
||||||
Extras CF valid
|
variant="outline"
|
||||||
</Badge>
|
className="text-[10px] border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-400 cursor-default"
|
||||||
{extractId && (
|
>
|
||||||
<Button
|
Extras CF
|
||||||
size="sm"
|
</Badge>
|
||||||
variant="ghost"
|
</TooltipTrigger>
|
||||||
className="h-7 w-7 p-0 text-emerald-600"
|
<TooltipContent>
|
||||||
title="Descarca extras CF"
|
{cfExpiry ? `Valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid"}
|
||||||
asChild
|
</TooltipContent>
|
||||||
>
|
</Tooltip>
|
||||||
<a
|
{extractId && (
|
||||||
href={`/api/ancpi/download?id=${extractId}`}
|
<Tooltip>
|
||||||
target="_blank"
|
<TooltipTrigger asChild>
|
||||||
rel="noopener noreferrer"
|
<Button
|
||||||
>
|
size="sm"
|
||||||
<Download className="h-3.5 w-3.5" />
|
variant="ghost"
|
||||||
</a>
|
className="h-7 w-7 p-0 text-emerald-600"
|
||||||
</Button>
|
asChild
|
||||||
)}
|
>
|
||||||
</div>
|
<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") {
|
if (cfStatus === "expired") {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<TooltipProvider>
|
||||||
<Badge
|
<div className="flex items-center gap-1">
|
||||||
variant="outline"
|
<Tooltip>
|
||||||
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400"
|
<TooltipTrigger asChild>
|
||||||
>
|
<Badge
|
||||||
Extras CF expirat
|
variant="outline"
|
||||||
</Badge>
|
className="text-[10px] border-orange-200 text-orange-600 dark:border-orange-800 dark:text-orange-400 cursor-default"
|
||||||
<EpayOrderButton
|
>
|
||||||
nrCadastral={r.nrCad}
|
Expirat
|
||||||
siruta={siruta}
|
</Badge>
|
||||||
judetName={selectedUat?.county ?? ""}
|
</TooltipTrigger>
|
||||||
uatName={selectedUat?.name ?? ""}
|
<TooltipContent>
|
||||||
/>
|
{cfExpiry ? `Expirat pe ${formatShortDate(cfExpiry)}` : "Extras CF expirat"}
|
||||||
</div>
|
</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") {
|
if (cfStatus === "processing") {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<TooltipProvider>
|
||||||
variant="outline"
|
<Tooltip>
|
||||||
className="text-[10px] border-yellow-200 text-yellow-600 dark:border-yellow-800 dark:text-yellow-400 animate-pulse"
|
<TooltipTrigger asChild>
|
||||||
>
|
<Badge
|
||||||
Se proceseaza...
|
variant="outline"
|
||||||
</Badge>
|
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 (
|
return (
|
||||||
@@ -2702,6 +2790,7 @@ export function ParcelSyncModule() {
|
|||||||
siruta={siruta}
|
siruta={siruta}
|
||||||
judetName={selectedUat?.county ?? ""}
|
judetName={selectedUat?.county ?? ""}
|
||||||
uatName={selectedUat?.name ?? ""}
|
uatName={selectedUat?.name ?? ""}
|
||||||
|
tooltipText="Comanda extras CF (1 credit)"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
Reference in New Issue
Block a user