@flowselections/twinfield-authenticatie-module-15-06-2026 2.0.4 → 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":"AAsdA,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"}
@@ -2,12 +2,16 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useState } from "react";
3
3
  // Maximum aantal pogingen in de klantenwachtrij (gedeeld contract met Klanten).
4
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
+ }
5
9
  import { useServerFn } from "@tanstack/react-start";
6
10
  import { Card, Button, Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
7
11
  import { ChevronUp, ChevronDown, Link2, Unlink } from "lucide-react";
8
12
  const twinfieldLogo = "/flowselections-assets/twinfield-authenticatie/twinfield-logo.svg";
9
13
  import { startTwinfieldOAuth, syncTwinfieldCentralAccounts, selectTwinfieldCentralAccount, listTwinfieldConnections, disconnectTwinfieldConnection, setTwinfieldConnectionOffice, listTwinfieldOfficesForConnection, getTwinfieldSyncOverview, autoRegisterTwinfieldCron, } from "../../lib/twinfield.functions.js";
10
- 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";
11
15
  import { listTwinfieldPushQueue, retryTwinfieldQueueItem, } from "../../lib/twinfield-adapter.functions.js";
12
16
  import { cn } from "../../lib/utils.js";
13
17
  /** PostgREST meldt ontbrekende kolommen als schema-cachefout. Dat betekent in de
@@ -63,6 +67,21 @@ function ConnectionCard({ conn, onChanged, onDisconnected, isAdmin = false, }) {
63
67
  const disconnect = useServerFn(disconnectTwinfieldConnection);
64
68
  const trigger = useServerFn(triggerTwinfieldSync);
65
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]);
66
85
  const [offices, setOffices] = useState([]);
67
86
  const [officesStatus, setOfficesStatus] = useState("empty");
68
87
  const [officesMessage, setOfficesMessage] = useState(null);
@@ -203,7 +222,11 @@ function ConnectionCard({ conn, onChanged, onDisconnected, isAdmin = false, }) {
203
222
  const cardNeedsReauth = conn.reauthorize_required ||
204
223
  (Array.isArray(conn.missing_scopes) && conn.missing_scopes.length > 0) ||
205
224
  needsReauth;
206
- 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: (() => {
207
230
  const selected = offices.find((o) => o.code === effectiveOfficeCode);
208
231
  if (!selected)
209
232
  return null;
@@ -365,12 +388,12 @@ export function TwinfieldAuthSettingsCard({ defaultExpanded = true, } = {}) {
365
388
  ? "Koppeling controleren…"
366
389
  : anyConnected
367
390
  ? "Verbonden"
368
- : "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 () => {
369
392
  // Optimistisch: verwijder rij direct uit UI en sla Central-sync over,
370
393
  // anders wordt de rij vanuit Central Auth meteen opnieuw opgehaald.
371
394
  setConnections((prev) => prev.filter((row) => row.id !== c.id));
372
395
  await loadAll({ skipCentralSync: true });
373
- } }, 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) => {
374
397
  // 'success' (deze module) en 'done' (Klanten-module) = afgehandeld.
375
398
  const isDone = q.status === "success" || q.status === "done";
376
399
  const isFailed = q.status === "dead" || q.status === "failed";
@@ -390,7 +413,7 @@ export function TwinfieldAuthSettingsCard({ defaultExpanded = true, } = {}) {
390
413
  : q.status === "pending"
391
414
  ? "In wachtrij"
392
415
  : q.status;
393
- 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: 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 () => {
394
417
  setRetrying(q.id);
395
418
  try {
396
419
  await retryQueueItem({ data: { id: q.id } });