@flowselections/twinfield-authenticatie-module-15-06-2026 2.0.2 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"TwinfieldAuthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TwinfieldAuthSettingsCard.tsx"],"names":[],"mappings":"AAmdA,wBAAgB,yBAAyB,CAAC,EACxC,eAAsB,GACvB,GAAE;IACD,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,+BAydL"}
1
+ {"version":3,"file":"TwinfieldAuthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TwinfieldAuthSettingsCard.tsx"],"names":[],"mappings":"AA0eA,wBAAgB,yBAAyB,CAAC,EACxC,eAAsB,GACvB,GAAE;IACD,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,+BAqeL"}
@@ -1,11 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
+ // Maximum aantal pogingen in de klantenwachtrij (gedeeld contract met Klanten).
4
+ const TWINFIELD_PUSH_MAX_ATTEMPTS = 5;
5
+ // Haalt alleen een technisch voorvoegsel "fieldN: " weg uit een Twinfield-melding.
6
+ function stripFieldPrefix(message) {
7
+ return message.replace(/(—\s*|^\s*)field\d+:\s*/i, "$1");
8
+ }
3
9
  import { useServerFn } from "@tanstack/react-start";
4
10
  import { Card, Button, Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
5
11
  import { ChevronUp, ChevronDown, Link2, Unlink } from "lucide-react";
6
12
  const twinfieldLogo = "/flowselections-assets/twinfield-authenticatie/twinfield-logo.svg";
7
13
  import { startTwinfieldOAuth, syncTwinfieldCentralAccounts, selectTwinfieldCentralAccount, listTwinfieldConnections, disconnectTwinfieldConnection, setTwinfieldConnectionOffice, listTwinfieldOfficesForConnection, getTwinfieldSyncOverview, autoRegisterTwinfieldCron, } from "../../lib/twinfield.functions.js";
8
- import { triggerTwinfieldSync, getTwinfieldSyncState, getTwinfieldPublicBaseUrl, setTwinfieldPublicBaseUrl, getTwinfieldPageSummary, } from "../../lib/twinfield-sync.functions.js";
14
+ import { triggerTwinfieldSync, getTwinfieldOfficeKind, getTwinfieldSyncState, getTwinfieldPublicBaseUrl, setTwinfieldPublicBaseUrl, getTwinfieldPageSummary, } from "../../lib/twinfield-sync.functions.js";
9
15
  import { listTwinfieldPushQueue, retryTwinfieldQueueItem, } from "../../lib/twinfield-adapter.functions.js";
10
16
  import { cn } from "../../lib/utils.js";
11
17
  /** PostgREST meldt ontbrekende kolommen als schema-cachefout. Dat betekent in de
@@ -61,6 +67,21 @@ function ConnectionCard({ conn, onChanged, onDisconnected, isAdmin = false, }) {
61
67
  const disconnect = useServerFn(disconnectTwinfieldConnection);
62
68
  const trigger = useServerFn(triggerTwinfieldSync);
63
69
  const startOAuthForCard = useServerFn(startTwinfieldOAuth);
70
+ const loadOfficeKind = useServerFn(getTwinfieldOfficeKind);
71
+ const [officeDemo, setOfficeDemo] = useState(null);
72
+ useEffect(() => {
73
+ let alive = true;
74
+ if (!conn.connected || !conn.office_code) {
75
+ setOfficeDemo(null);
76
+ return;
77
+ }
78
+ loadOfficeKind({ data: { connection_id: conn.id } })
79
+ .then((r) => { if (alive)
80
+ setOfficeDemo(r?.demo ?? null); })
81
+ .catch(() => { if (alive)
82
+ setOfficeDemo(null); });
83
+ return () => { alive = false; };
84
+ }, [conn.id, conn.connected, conn.office_code, loadOfficeKind]);
64
85
  const [offices, setOffices] = useState([]);
65
86
  const [officesStatus, setOfficesStatus] = useState("empty");
66
87
  const [officesMessage, setOfficesMessage] = useState(null);
@@ -201,7 +222,11 @@ function ConnectionCard({ conn, onChanged, onDisconnected, isAdmin = false, }) {
201
222
  const cardNeedsReauth = conn.reauthorize_required ||
202
223
  (Array.isArray(conn.missing_scopes) && conn.missing_scopes.length > 0) ||
203
224
  needsReauth;
204
- return (_jsxs("div", { className: "rounded-lg border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "font-semibold text-foreground truncate", children: displayName }), _jsx(Badge, { variant: cardNeedsReauth ? "destructive" : conn.connected ? "default" : "secondary", className: "font-normal", children: cardNeedsReauth ? "Autorisatie vereist" : conn.connected ? "Verbonden" : "Onvolledig" }), _jsx(Badge, { variant: "outline", className: "font-normal", children: conn.environment === "staging" || conn.environment === "sandbox" ? "Test" : "Live" })] }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [effectiveOfficeCode ? (_jsx("span", { className: "font-mono", children: effectiveOfficeCode })) : (_jsx("span", { children: "Kies hieronder een administratie om te kunnen synchroniseren." })), isAdmin && conn.account_key && (_jsxs("span", { className: "ml-2", children: ["\u00B7 account ", _jsx("span", { className: "font-mono", children: conn.account_key })] })), conn.connected_at && (_jsxs("span", { className: "ml-2", children: ["\u00B7 gekoppeld ", relativeTime(conn.connected_at)] }))] }), conn.connected && !effectiveOfficeCode && !cardNeedsReauth && (_jsx("div", { className: "mt-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-xs text-foreground", children: "Kies een administratie om te beginnen met synchroniseren." })), cardNeedsReauth && (_jsxs("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: [_jsx("div", { className: "font-medium", children: conn.reauth_message ?? "De toestemming bij Twinfield moet worden vernieuwd. Autoriseer opnieuw." }), isAdmin && Array.isArray(conn.missing_scopes) && conn.missing_scopes.length > 0 && (_jsxs("div", { className: "mt-0.5", children: ["Ontbrekende scopes: ", conn.missing_scopes.join(", ")] })), _jsxs(Button, { size: "sm", variant: "outline", className: "mt-2 border-destructive/50 text-destructive hover:bg-destructive/10", onClick: handleReauthorize, disabled: reauthorizing, children: [_jsx(Link2, { className: "h-3.5 w-3.5 mr-1.5" }), reauthorizing ? "Bezig…" : "Opnieuw autoriseren"] })] }))] }), _jsxs(Button, { size: "sm", variant: "ghost", onClick: handleDisconnect, disabled: disconnecting, className: "text-muted-foreground hover:text-destructive", children: [_jsx(Unlink, { className: "h-4 w-4 mr-1.5" }), disconnecting ? "Bezig…" : "Verbreken"] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Administratie" }), offices.length > 0 ? (_jsxs(Select, { value: effectiveOfficeCode ?? undefined, onValueChange: handleOfficeChange, disabled: savingOffice, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een administratie", children: (() => {
225
+ return (_jsxs("div", { className: "rounded-lg border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "font-semibold text-foreground truncate", children: displayName }), _jsx(Badge, { variant: cardNeedsReauth ? "destructive" : conn.connected ? "default" : "secondary", className: "font-normal", children: cardNeedsReauth ? "Autorisatie vereist" : conn.connected ? "Verbonden" : "Onvolledig" }), _jsx(Badge, { variant: "outline", className: "font-normal", children: officeDemo === true
226
+ ? "Testadministratie"
227
+ : conn.environment === "staging" || conn.environment === "sandbox"
228
+ ? "Testomgeving"
229
+ : "Live" })] }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [effectiveOfficeCode ? (_jsx("span", { className: "font-mono", children: effectiveOfficeCode })) : (_jsx("span", { children: "Kies hieronder een administratie om te kunnen synchroniseren." })), isAdmin && conn.account_key && (_jsxs("span", { className: "ml-2", children: ["\u00B7 account ", _jsx("span", { className: "font-mono", children: conn.account_key })] })), conn.connected_at && (_jsxs("span", { className: "ml-2", children: ["\u00B7 gekoppeld ", relativeTime(conn.connected_at)] }))] }), conn.connected && !effectiveOfficeCode && !cardNeedsReauth && (_jsx("div", { className: "mt-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-xs text-foreground", children: "Kies een administratie om te beginnen met synchroniseren." })), cardNeedsReauth && (_jsxs("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: [_jsx("div", { className: "font-medium", children: conn.reauth_message ?? "De toestemming bij Twinfield moet worden vernieuwd. Autoriseer opnieuw." }), isAdmin && Array.isArray(conn.missing_scopes) && conn.missing_scopes.length > 0 && (_jsxs("div", { className: "mt-0.5", children: ["Ontbrekende scopes: ", conn.missing_scopes.join(", ")] })), _jsxs(Button, { size: "sm", variant: "outline", className: "mt-2 border-destructive/50 text-destructive hover:bg-destructive/10", onClick: handleReauthorize, disabled: reauthorizing, children: [_jsx(Link2, { className: "h-3.5 w-3.5 mr-1.5" }), reauthorizing ? "Bezig…" : "Opnieuw autoriseren"] })] }))] }), _jsxs(Button, { size: "sm", variant: "ghost", onClick: handleDisconnect, disabled: disconnecting, className: "text-muted-foreground hover:text-destructive", children: [_jsx(Unlink, { className: "h-4 w-4 mr-1.5" }), disconnecting ? "Bezig…" : "Verbreken"] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Administratie" }), offices.length > 0 ? (_jsxs(Select, { value: effectiveOfficeCode ?? undefined, onValueChange: handleOfficeChange, disabled: savingOffice, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een administratie", children: (() => {
205
230
  const selected = offices.find((o) => o.code === effectiveOfficeCode);
206
231
  if (!selected)
207
232
  return null;
@@ -363,12 +388,12 @@ export function TwinfieldAuthSettingsCard({ defaultExpanded = true, } = {}) {
363
388
  ? "Koppeling controleren…"
364
389
  : anyConnected
365
390
  ? "Verbonden"
366
- : "Niet verbonden" })] })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "E\u00E9n Twinfield-account per omgeving. Verbreek eerst de huidige koppeling om te wisselen naar een ander account." })] })] }), _jsx("button", { type: "button", onClick: () => setIsExpanded((v) => !v), className: "shrink-0 p-1 rounded-md hover:bg-muted transition-colors", "aria-label": isExpanded ? "Samenvouwen" : "Uitklappen", children: isExpanded ? (_jsx(ChevronUp, { className: "h-5 w-5 text-muted-foreground" })) : (_jsx(ChevronDown, { className: "h-5 w-5 text-muted-foreground" })) })] }), isExpanded && (_jsxs("div", { className: "space-y-6 p-6", children: [summary && _jsx(SummaryPanel, { summary: summary }), centralNotice && (_jsx("div", { className: "rounded-lg border bg-muted/30 px-4 py-3 text-sm text-muted-foreground", "aria-live": "polite", children: centralNotice })), !hasAny && !loading && (_jsxs("div", { className: "rounded-lg border border-dashed p-8 text-center space-y-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: "Nog niet verbonden met Twinfield" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Log in bij Twinfield en geef eenmalig toestemming. Wij bewaren de toegang veilig en verversen tokens automatisch." })] }), _jsxs(Button, { size: "lg", onClick: handleConnect, disabled: connecting, children: [_jsx(Link2, { className: "h-4 w-4 mr-2" }), connecting ? "Koppelen…" : "Verbind met Twinfield"] })] })), selection && (selection.needs_choice || selection.replaced || actionIgnored(selection.ignored).length > 0) && (_jsxs("div", { className: "rounded-lg border p-4 space-y-3 text-sm", children: [selection.replaced && (_jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2", children: ["Koppeling vervangen: ", _jsx("span", { className: "font-mono", children: selection.replaced.from }), " (", selection.replaced.reason, ") \u2192 ", _jsx("span", { className: "font-mono", children: selection.replaced.to }), "."] })), selection.needs_choice && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "font-semibold text-foreground", children: "Kies Twinfield-account" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Er zijn meerdere bruikbare accounts. Kies welk account deze module gebruikt." }), _jsx("div", { className: "divide-y rounded-md border", children: selection.choices.map((ch) => (_jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 text-xs", children: [_jsx("div", { className: "font-mono text-foreground", children: ch.account_key }), _jsxs("div", { className: "text-muted-foreground", children: [ch.environment === "live" ? "Live" : "Test", " \u00B7", " ", ch.company_name ?? ch.office_code ?? "administratie nog niet bekend"] })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: choosing !== null, onClick: () => handleChoose(ch.account_key), children: choosing === ch.account_key ? "Bezig…" : "Kiezen" })] }, ch.account_key))) })] })), actionIgnored(selection.ignored).length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Account vraagt om actie: koppel opnieuw met Twinfield" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-xs text-muted-foreground", children: actionIgnored(selection.ignored).map((ig) => (_jsxs("li", { children: [_jsx("span", { className: "font-mono", children: ig.account_key }), " \u00B7 ", ig.reason] }, ig.account_key))) })] }))] })), hasAny && (_jsxs("div", { className: "space-y-3", children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: "Gekoppelde administratie" }), _jsx("div", { className: "space-y-3", children: connections.map((c) => (_jsx(ConnectionCard, { isAdmin: isAdmin, conn: c, onChanged: () => loadAll(), onDisconnected: async () => {
391
+ : "Niet verbonden" })] })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "E\u00E9n Twinfield-account per omgeving. Verbreek eerst de huidige koppeling om te wisselen naar een ander account." })] })] }), _jsx("button", { type: "button", onClick: () => setIsExpanded((v) => !v), className: "shrink-0 p-1 rounded-md hover:bg-muted transition-colors", "aria-label": isExpanded ? "Samenvouwen" : "Uitklappen", children: isExpanded ? (_jsx(ChevronUp, { className: "h-5 w-5 text-muted-foreground" })) : (_jsx(ChevronDown, { className: "h-5 w-5 text-muted-foreground" })) })] }), isExpanded && (_jsxs("div", { className: "space-y-6 p-6", children: [summary && _jsx(SummaryPanel, { summary: summary }), centralNotice && (_jsx("div", { className: "rounded-lg border bg-muted/30 px-4 py-3 text-sm text-muted-foreground", "aria-live": "polite", children: centralNotice })), !hasAny && !loading && (_jsxs("div", { className: "rounded-lg border border-dashed p-8 text-center space-y-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: "Nog niet verbonden met Twinfield" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Log in bij Twinfield en geef eenmalig toestemming. Wij bewaren de toegang veilig en verversen tokens automatisch." })] }), _jsxs(Button, { size: "lg", onClick: handleConnect, disabled: connecting, children: [_jsx(Link2, { className: "h-4 w-4 mr-2" }), connecting ? "Koppelen…" : "Verbind met Twinfield"] })] })), selection && (selection.needs_choice || selection.replaced || actionIgnored(selection.ignored).length > 0) && (_jsxs("div", { className: "rounded-lg border p-4 space-y-3 text-sm", children: [selection.replaced && (_jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2", children: ["Koppeling vervangen: ", _jsx("span", { className: "font-mono", children: selection.replaced.from }), " (", selection.replaced.reason, ") \u2192 ", _jsx("span", { className: "font-mono", children: selection.replaced.to }), "."] })), selection.needs_choice && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "font-semibold text-foreground", children: "Kies Twinfield-account" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Er zijn meerdere bruikbare accounts. Kies welk account deze module gebruikt." }), _jsx("div", { className: "divide-y rounded-md border", children: selection.choices.map((ch) => (_jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 text-xs", children: [isAdmin && _jsx("div", { className: "font-mono text-foreground", children: ch.account_key }), _jsxs("div", { className: "text-muted-foreground", children: [ch.environment === "live" ? "Live" : "Test", " \u00B7", " ", ch.company_name ?? ch.office_code ?? "administratie nog niet bekend"] })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: choosing !== null, onClick: () => handleChoose(ch.account_key), children: choosing === ch.account_key ? "Bezig…" : "Kiezen" })] }, ch.account_key))) })] })), actionIgnored(selection.ignored).length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Account vraagt om actie: koppel opnieuw met Twinfield" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-xs text-muted-foreground", children: actionIgnored(selection.ignored).map((ig) => (_jsxs("li", { children: [_jsx("span", { className: "font-mono", children: ig.account_key }), " \u00B7 ", ig.reason] }, ig.account_key))) })] }))] })), hasAny && (_jsxs("div", { className: "space-y-3", children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: "Gekoppelde administratie" }), _jsx("div", { className: "space-y-3", children: connections.map((c) => (_jsx(ConnectionCard, { isAdmin: isAdmin, conn: c, onChanged: () => loadAll(), onDisconnected: async () => {
367
392
  // Optimistisch: verwijder rij direct uit UI en sla Central-sync over,
368
393
  // anders wordt de rij vanuit Central Auth meteen opnieuw opgehaald.
369
394
  setConnections((prev) => prev.filter((row) => row.id !== c.id));
370
395
  await loadAll({ skipCentralSync: true });
371
- } }, c.id))) })] })), connections.find((c) => c.connected) && (_jsx(SyncOverviewSection, { connectionId: connections.find((c) => c.connected).id, part: "defaults" })), anyConnected && pushQueue.length > 0 && (_jsxs("div", { className: "rounded-lg border p-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-foreground", children: "Uitgaande klantwijzigingen naar Twinfield" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "De laatste 20 klanten die vanuit de klantenlijst naar Twinfield zijn gestuurd. Facturen staan hier niet in." })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void loadAll(), children: "Vernieuwen" })] }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: pushQueue.map((q) => {
396
+ } }, c.id))) })] })), connections.find((c) => c.connected) && (_jsx(SyncOverviewSection, { connectionId: connections.find((c) => c.connected).id, part: "defaults" })), anyConnected && pushQueue.length > 0 && (_jsxs("div", { className: "rounded-lg border p-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-foreground", children: "Uitgaande klantwijzigingen naar Twinfield" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "De laatste 20 klanten die vanuit de klantenlijst naar Twinfield zijn gestuurd, met per klant de laatste verzending. Facturen staan hier niet in." })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void loadAll(), children: "Vernieuwen" })] }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: pushQueue.map((q) => {
372
397
  // 'success' (deze module) en 'done' (Klanten-module) = afgehandeld.
373
398
  const isDone = q.status === "success" || q.status === "done";
374
399
  const isFailed = q.status === "dead" || q.status === "failed";
@@ -388,7 +413,7 @@ export function TwinfieldAuthSettingsCard({ defaultExpanded = true, } = {}) {
388
413
  : q.status === "pending"
389
414
  ? "In wachtrij"
390
415
  : q.status;
391
- return (_jsxs("div", { className: "flex items-start justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: variant, children: label }), isFailed && (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["poging ", q.attempts, "/5"] })), _jsx("span", { className: "text-sm text-foreground", children: q.customer_name ?? "Onbekende klant" }), q.external_id && (_jsxs("span", { className: "text-xs font-mono text-muted-foreground", children: ["debiteur ", q.external_id] }))] }), q.last_error && (_jsx("div", { className: "mt-1 text-xs text-muted-foreground break-all", children: q.last_error })), _jsx("div", { className: "mt-0.5 text-[11px] text-muted-foreground", children: relativeTime(q.updated_at ?? q.created_at) })] }), isFailed && (_jsx(Button, { size: "sm", variant: "outline", disabled: retrying === q.id, onClick: async () => {
416
+ return (_jsxs("div", { className: "flex items-start justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: variant, children: label }), isFailed && (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["poging ", Math.min(Number(q.attempts ?? 0), TWINFIELD_PUSH_MAX_ATTEMPTS), "/", TWINFIELD_PUSH_MAX_ATTEMPTS] })), _jsx("span", { className: "text-sm text-foreground", children: q.customer_name ?? "Onbekende klant" }), q.external_id && (_jsxs("span", { className: "text-xs font-mono text-muted-foreground", children: ["debiteur ", q.external_id] }))] }), q.last_error && (_jsx("div", { className: "mt-1 text-xs text-muted-foreground break-all", children: stripFieldPrefix(q.last_error) })), isAdmin && q.last_error && stripFieldPrefix(q.last_error) !== q.last_error && (_jsxs("details", { className: "mt-1 text-[11px] text-muted-foreground", children: [_jsx("summary", { className: "cursor-pointer", children: "Details voor beheer" }), _jsx("div", { className: "break-all font-mono", children: q.last_error })] })), Number(q.send_count ?? 1) > 1 && (_jsxs("div", { className: "mt-0.5 text-[11px] text-muted-foreground", children: [q.send_count, " keer verstuurd"] })), _jsx("div", { className: "mt-0.5 text-[11px] text-muted-foreground", children: relativeTime(q.updated_at ?? q.created_at) })] }), isFailed && (_jsx(Button, { size: "sm", variant: "outline", disabled: retrying === q.id, onClick: async () => {
392
417
  setRetrying(q.id);
393
418
  try {
394
419
  await retryQueueItem({ data: { id: q.id } });