@flowselections/twinfield-authenticatie-module-15-06-2026 1.1.4 → 1.3.1

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.
Files changed (59) hide show
  1. package/dist-lib/components/settings/TwinfieldAuthSettingsCard.d.ts +1 -1
  2. package/dist-lib/components/settings/TwinfieldAuthSettingsCard.d.ts.map +1 -1
  3. package/dist-lib/components/settings/TwinfieldAuthSettingsCard.js +228 -241
  4. package/dist-lib/index.d.ts +2 -1
  5. package/dist-lib/index.d.ts.map +1 -1
  6. package/dist-lib/index.js +3 -4
  7. package/dist-lib/integrations/supabase/auth-middleware.d.ts +9712 -4587
  8. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  9. package/dist-lib/integrations/supabase/client.d.ts +9712 -4587
  10. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  11. package/dist-lib/integrations/supabase/client.js +4 -2
  12. package/dist-lib/integrations/supabase/client.server.d.ts +9712 -4587
  13. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/twinfield-admin.server.d.ts +9712 -4587
  15. package/dist-lib/integrations/supabase/twinfield-admin.server.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/types.d.ts +9878 -4600
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/types.js +59 -8
  19. package/dist-lib/lib/accounting/twinfield/actions.functions.d.ts +60338 -23940
  20. package/dist-lib/lib/accounting/twinfield/actions.functions.d.ts.map +1 -1
  21. package/dist-lib/lib/accounting/twinfield/actions.functions.js +145 -38
  22. package/dist-lib/lib/accounting/twinfield/client.server.d.ts +1 -5
  23. package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -1
  24. package/dist-lib/lib/accounting/twinfield/conformance.functions.d.ts +9712 -4587
  25. package/dist-lib/lib/accounting/twinfield/conformance.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/accounting/twinfield/conformance.functions.js +4 -16
  27. package/dist-lib/lib/accounting/twinfield/mapping.d.ts +25 -0
  28. package/dist-lib/lib/accounting/twinfield/mapping.d.ts.map +1 -1
  29. package/dist-lib/lib/accounting/twinfield/mapping.js +81 -22
  30. package/dist-lib/lib/accounting/twinfield/session.server.d.ts.map +1 -1
  31. package/dist-lib/lib/accounting/twinfield/session.server.js +6 -6
  32. package/dist-lib/lib/accounting/twinfield/writes.functions.d.ts +47683 -0
  33. package/dist-lib/lib/accounting/twinfield/writes.functions.d.ts.map +1 -0
  34. package/dist-lib/lib/accounting/twinfield/writes.functions.js +182 -0
  35. package/dist-lib/lib/central/central-accounts.server.d.ts +33 -0
  36. package/dist-lib/lib/central/central-accounts.server.d.ts.map +1 -0
  37. package/dist-lib/lib/central/central-accounts.server.js +217 -0
  38. package/dist-lib/lib/central/central-config.server.d.ts +9 -0
  39. package/dist-lib/lib/central/central-config.server.d.ts.map +1 -0
  40. package/dist-lib/lib/central/central-config.server.js +24 -0
  41. package/dist-lib/lib/central/central-token.server.d.ts +11 -0
  42. package/dist-lib/lib/central/central-token.server.d.ts.map +1 -0
  43. package/dist-lib/lib/central/central-token.server.js +44 -0
  44. package/dist-lib/lib/twinfield-adapter.functions.d.ts +22467 -12217
  45. package/dist-lib/lib/twinfield-adapter.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/twinfield-sync.functions.d.ts +65503 -7559
  47. package/dist-lib/lib/twinfield-sync.functions.d.ts.map +1 -1
  48. package/dist-lib/lib/twinfield-sync.functions.js +52 -0
  49. package/dist-lib/lib/twinfield-sync.server.d.ts +94 -1
  50. package/dist-lib/lib/twinfield-sync.server.d.ts.map +1 -1
  51. package/dist-lib/lib/twinfield-sync.server.js +1035 -70
  52. package/dist-lib/lib/twinfield-token.server.d.ts +13 -28
  53. package/dist-lib/lib/twinfield-token.server.d.ts.map +1 -1
  54. package/dist-lib/lib/twinfield-token.server.js +70 -248
  55. package/dist-lib/lib/twinfield.functions.d.ts +132122 -1409
  56. package/dist-lib/lib/twinfield.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/twinfield.functions.js +314 -207
  58. package/package.json +3 -3
  59. package/public/flowselections-assets/twinfield-authenticatie/twinfield-logo.svg +217 -217
@@ -1,4 +1,4 @@
1
- export declare function TwinfieldAuthSettingsCard({ defaultExpanded }?: {
1
+ export declare function TwinfieldAuthSettingsCard({ defaultExpanded, }?: {
2
2
  defaultExpanded?: boolean;
3
3
  }): import("react").JSX.Element;
4
4
  //# sourceMappingURL=TwinfieldAuthSettingsCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TwinfieldAuthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TwinfieldAuthSettingsCard.tsx"],"names":[],"mappings":"AA6FA,wBAAgB,yBAAyB,CAAC,EAAE,eAAuB,EAAE,GAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,+BAuoBxG"}
1
+ {"version":3,"file":"TwinfieldAuthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TwinfieldAuthSettingsCard.tsx"],"names":[],"mappings":"AAmTA,wBAAgB,yBAAyB,CAAC,EACxC,eAAsB,GACvB,GAAE;IACD,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,+BA6UL"}
@@ -1,22 +1,34 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { useServerFn } from "@tanstack/react-start";
4
- import { Card, Button, Input, Label, Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
5
- import { ChevronUp, ChevronDown } from "lucide-react";
4
+ import { Card, Button, Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
5
+ import { ChevronUp, ChevronDown, Link2, Unlink } from "lucide-react";
6
6
  const twinfieldLogo = "/flowselections-assets/twinfield-authenticatie/twinfield-logo.svg";
7
- import { saveTwinfieldSettings, getTwinfieldStatus, testTwinfieldConnection, setTwinfieldActiveEnvironment, listTwinfieldOffices, } from "../../lib/twinfield.functions";
7
+ import { startTwinfieldOAuth, syncTwinfieldCentralAccounts, listTwinfieldConnections, disconnectTwinfieldConnection, setTwinfieldConnectionOffice, listTwinfieldOfficesForConnection, } from "../../lib/twinfield.functions";
8
8
  import { triggerTwinfieldSync, getTwinfieldSyncState, } from "../../lib/twinfield-sync.functions";
9
9
  import { listTwinfieldPushQueue, retryTwinfieldQueueItem, } from "../../lib/twinfield-adapter.functions";
10
10
  import { cn } from "../../lib/utils";
11
- const REDIRECT_URI = "https://twinfield-authenticatie-module-15-06-2026.lovable.app/api/public/twinfield/callback";
12
- // ----------------------------------------------------------------------------
13
- // Helpers
14
- // ----------------------------------------------------------------------------
15
- function StepNumber({ n }) {
16
- return (_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground text-sm font-semibold", children: n }));
11
+ function hasTwinfieldReturnParams() {
12
+ if (typeof window === "undefined")
13
+ return false;
14
+ const params = new URLSearchParams(window.location.search);
15
+ return ["success", "code", "state", "accountKey", "account_key", "error"].some((key) => params.has(key));
17
16
  }
18
- function Step({ n, title, description, children, }) {
19
- return (_jsxs("div", { className: "flex gap-4", children: [_jsx(StepNumber, { n: n }), _jsxs("div", { className: "flex-1 space-y-3 pt-0.5", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: title }), description && (_jsx("p", { className: "text-sm text-muted-foreground mt-1", children: description }))] }), _jsx("div", { children: children })] })] }));
17
+ function clearTwinfieldReturnParams() {
18
+ if (typeof window === "undefined")
19
+ return;
20
+ const url = new URL(window.location.href);
21
+ const keys = ["success", "code", "state", "accountKey", "account_key", "error"];
22
+ let changed = false;
23
+ for (const key of keys) {
24
+ if (url.searchParams.has(key)) {
25
+ url.searchParams.delete(key);
26
+ changed = true;
27
+ }
28
+ }
29
+ if (changed) {
30
+ window.history.replaceState({}, "", `${url.pathname}${url.search}${url.hash}`);
31
+ }
20
32
  }
21
33
  function relativeTime(iso) {
22
34
  if (!iso)
@@ -33,272 +45,247 @@ function relativeTime(iso) {
33
45
  const d = Math.floor(hr / 24);
34
46
  return `${d} dag${d === 1 ? "" : "en"} geleden`;
35
47
  }
36
- // ----------------------------------------------------------------------------
37
- // Main composed card
38
- // ----------------------------------------------------------------------------
39
- export function TwinfieldAuthSettingsCard({ defaultExpanded = false } = {}) {
40
- const fetchStatus = useServerFn(getTwinfieldStatus);
41
- const saveSettings = useServerFn(saveTwinfieldSettings);
42
- const testConnection = useServerFn(testTwinfieldConnection);
43
- const setEnv = useServerFn(setTwinfieldActiveEnvironment);
48
+ function ConnectionCard({ conn, onChanged, onDisconnected, }) {
49
+ const loadOffices = useServerFn(listTwinfieldOfficesForConnection);
50
+ const saveOffice = useServerFn(setTwinfieldConnectionOffice);
51
+ const disconnect = useServerFn(disconnectTwinfieldConnection);
44
52
  const trigger = useServerFn(triggerTwinfieldSync);
45
- const getState = useServerFn(getTwinfieldSyncState);
46
- const fetchOffices = useServerFn(listTwinfieldOffices);
47
- // Connection state
48
- const [clientId, setClientId] = useState("");
49
- const [clientSecret, setClientSecret] = useState("");
50
- const [environment, setEnvironment] = useState("sandbox");
51
- const [administrationCode, setAdministrationCode] = useState("");
52
- const [loading, setLoading] = useState(true);
53
- const [saving, setSaving] = useState(false);
54
- const [savingAdmin, setSavingAdmin] = useState(false);
55
- const [testing, setTesting] = useState(false);
56
- const [connected, setConnected] = useState(false);
57
- const [hasSecret, setHasSecret] = useState(false);
58
- const [hasClientId, setHasClientId] = useState(false);
59
- const [existingClientId, setExistingClientId] = useState("");
60
- // Sync state
61
- const [busy, setBusy] = useState(null);
62
- const [counts, setCounts] = useState({ customers: 0, invoices: 0 });
63
- const [states, setStates] = useState([]);
64
53
  const [offices, setOffices] = useState([]);
65
- const [isExpanded, setIsExpanded] = useState(defaultExpanded);
66
- const fetchPushQueue = useServerFn(listTwinfieldPushQueue);
67
- const retryQueueItem = useServerFn(retryTwinfieldQueueItem);
68
- const [pushQueue, setPushQueue] = useState([]);
69
- const [retrying, setRetrying] = useState(null);
70
- const loadPushQueue = async () => {
71
- try {
72
- const res = await fetchPushQueue();
73
- setPushQueue(res.items ?? []);
74
- }
75
- catch {
76
- // stille fout — push-queue is operationeel, niet kritiek voor de UI
77
- }
78
- };
79
- const loadStatus = async () => {
80
- try {
81
- const res = await fetchStatus();
82
- setConnected(!!res.connected);
83
- setHasSecret(!!res.has_client_secret);
84
- setEnvironment(res.active_environment ?? "sandbox");
85
- if (res.settings) {
86
- const existing = res.settings.client_id ?? "";
87
- setExistingClientId(existing);
88
- setHasClientId(!!existing);
89
- setClientId("");
90
- setAdministrationCode(res.settings.administration_code ?? "");
54
+ const [loadingOffices, setLoadingOffices] = useState(false);
55
+ const [savingOffice, setSavingOffice] = useState(false);
56
+ const [optimisticOffice, setOptimisticOffice] = useState(null);
57
+ const [disconnecting, setDisconnecting] = useState(false);
58
+ const [busy, setBusy] = useState(null);
59
+ useEffect(() => {
60
+ let cancelled = false;
61
+ (async () => {
62
+ setLoadingOffices(true);
63
+ try {
64
+ const res = await loadOffices({ data: { id: conn.id } });
65
+ if (!cancelled) {
66
+ const list = Array.isArray(res?.offices) ? res.offices : [];
67
+ setOffices(list);
68
+ }
91
69
  }
92
- else {
93
- setExistingClientId("");
94
- setHasClientId(false);
95
- setClientId("");
96
- setAdministrationCode("");
70
+ catch {
71
+ /* silent */
97
72
  }
98
- }
99
- catch {
100
- toast.error("Status kon niet geladen worden.");
101
- }
102
- finally {
103
- setLoading(false);
104
- }
105
- };
106
- const loadSyncState = async () => {
107
- try {
108
- const res = await getState();
109
- const safeStates = Array.isArray(res?.states) ? res.states : [];
110
- setStates(safeStates);
111
- setCounts(res?.counts ?? { customers: 0, invoices: 0 });
112
- }
113
- catch {
114
- // silent
115
- }
116
- };
117
- const loadOffices = async () => {
118
- try {
119
- const res = await fetchOffices();
120
- const list = Array.isArray(res?.offices) ? res.offices : [];
121
- setOffices(list);
122
- // Auto-vullen wanneer er precies één administratie is en het veld leeg is
123
- setAdministrationCode((cur) => !cur && list.length === 1 ? list[0].code : cur);
124
- }
125
- catch {
126
- // silent — handmatig typen blijft mogelijk
127
- }
128
- };
129
- useEffect(() => {
130
- loadStatus();
131
- loadSyncState();
132
- loadPushQueue();
133
- }, []);
73
+ finally {
74
+ if (!cancelled)
75
+ setLoadingOffices(false);
76
+ }
77
+ })();
78
+ return () => {
79
+ cancelled = true;
80
+ };
81
+ }, [conn.id, loadOffices]);
82
+ // Wanneer server-waarde overeenkomt met optimistische keuze: opruimen.
134
83
  useEffect(() => {
135
- if (connected)
136
- loadOffices();
137
- // eslint-disable-next-line react-hooks/exhaustive-deps
138
- }, [connected]);
139
- const handleSave = async () => {
140
- const effectiveClientId = clientId.trim() || existingClientId;
141
- if (!effectiveClientId) {
142
- toast.error("Client ID is verplicht");
143
- return;
84
+ if (optimisticOffice && conn.office_code === optimisticOffice.code) {
85
+ setOptimisticOffice(null);
144
86
  }
145
- setSaving(true);
87
+ }, [conn.office_code, optimisticOffice]);
88
+ const handleOfficeChange = async (code) => {
89
+ const picked = offices.find((o) => o.code === code);
90
+ setOptimisticOffice({ code, name: picked?.name ?? null });
91
+ setSavingOffice(true);
146
92
  try {
147
- await saveSettings({
148
- data: {
149
- client_id: effectiveClientId,
150
- client_secret: clientSecret || undefined,
151
- environment,
152
- administration_code: administrationCode || undefined,
153
- },
93
+ await saveOffice({
94
+ data: { id: conn.id, office_code: code, company_name: picked?.name ?? null },
154
95
  });
155
- setClientId("");
156
- setClientSecret("");
157
- toast.success("Wijzigingen opgeslagen.");
158
- await loadStatus();
96
+ toast.success("Administratie opgeslagen.");
97
+ await onChanged();
159
98
  }
160
99
  catch (e) {
161
- const msg = e?.message;
162
- toast.error(msg && msg.length < 120 ? msg : "Opslaan mislukt.");
100
+ setOptimisticOffice(null);
101
+ toast.error(e?.message ?? "Opslaan mislukt.");
163
102
  }
164
103
  finally {
165
- setSaving(false);
104
+ setSavingOffice(false);
166
105
  }
167
106
  };
168
- const handleSaveAdmin = async () => {
169
- if (!existingClientId) {
170
- toast.error("Vul eerst je verbindingsgegevens in.");
107
+ const handleDisconnect = async () => {
108
+ if (!confirm("Weet je zeker dat je deze Twinfield-koppeling wilt verbreken?"))
171
109
  return;
172
- }
173
- setSavingAdmin(true);
110
+ setDisconnecting(true);
174
111
  try {
175
- await saveSettings({
176
- data: {
177
- client_id: existingClientId,
178
- environment,
179
- administration_code: administrationCode || undefined,
180
- },
181
- });
182
- toast.success("Instellingen opgeslagen.");
183
- await loadStatus();
112
+ const res = (await disconnect({ data: { id: conn.id } }));
113
+ if (res?.centralRevokeFailed) {
114
+ toast.warning(`Lokaal verbroken, maar Central Auth kon niet worden ingetrokken: ${res.centralRevokeMessage ?? "onbekende fout"}. Controleer Central Auth.`);
115
+ }
116
+ else {
117
+ toast.success("Verbinding verbroken.");
118
+ }
119
+ await onDisconnected();
184
120
  }
185
121
  catch (e) {
186
- const msg = e?.message;
187
- toast.error(msg && msg.length < 120 ? msg : "Opslaan mislukt.");
122
+ toast.error(e?.message ?? "Verbreken mislukt.");
188
123
  }
189
124
  finally {
190
- setSavingAdmin(false);
125
+ setDisconnecting(false);
191
126
  }
192
127
  };
193
- const handleTest = async () => {
194
- setTesting(true);
128
+ const runSync = async (entity) => {
129
+ setBusy(entity);
195
130
  try {
196
- const res = await testConnection({ data: undefined });
197
- if (res.ok)
198
- toast.success(res.message);
199
- else
200
- toast.error(res.message);
131
+ const res = await trigger({ data: { entity_type: entity } });
132
+ if (res.ok) {
133
+ toast.success(`${entity === "customers" ? "Debiteuren" : "Verkoopfacturen"} bijgewerkt (${res.upserted} rij(en)).`);
134
+ }
135
+ else {
136
+ toast.error(`Bijwerken mislukt: ${res.error ?? "onbekende fout"}`);
137
+ }
138
+ await onChanged();
201
139
  }
202
- catch {
203
- toast.error("Verbinding testen is mislukt.");
140
+ catch (e) {
141
+ toast.error(e?.message ?? "Bijwerken mislukt.");
204
142
  }
205
143
  finally {
206
- setTesting(false);
144
+ setBusy(null);
207
145
  }
208
146
  };
209
- const handleEnvChange = async (newEnv) => {
210
- if (newEnv === environment)
211
- return;
212
- setEnvironment(newEnv);
147
+ const effectiveOfficeCode = conn.office_code ?? optimisticOffice?.code ?? null;
148
+ const effectiveOfficeName = conn.company_name ?? optimisticOffice?.name ?? null;
149
+ const displayName = effectiveOfficeName ?? effectiveOfficeCode ?? conn.company_code ?? "Nog geen administratie gekozen";
150
+ 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: conn.connected ? "default" : "secondary", className: "font-normal", children: conn.connected ? "Verbonden" : "Onvolledig" }), _jsx(Badge, { variant: "outline", className: "font-normal", children: 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." })), conn.connected_at && (_jsxs("span", { className: "ml-2", children: ["\u00B7 gekoppeld ", relativeTime(conn.connected_at)] }))] })] }), _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: (() => {
151
+ const selected = offices.find((o) => o.code === effectiveOfficeCode);
152
+ if (!selected)
153
+ return null;
154
+ return (_jsxs("span", { className: "flex items-baseline gap-2 truncate", children: [_jsx("span", { className: "truncate", children: selected.name ?? selected.code }), _jsx("span", { className: "text-[11px] text-muted-foreground font-mono", children: selected.code })] }));
155
+ })() }) }), _jsx(SelectContent, { children: offices.map((o) => (_jsx(SelectItem, { value: o.code, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { children: o.name ?? o.code }), _jsx("span", { className: "text-[11px] text-muted-foreground font-mono", children: o.code })] }) }, o.code))) })] })) : (_jsx("div", { className: "text-xs text-muted-foreground", children: loadingOffices ? "Administraties ophalen…" : "Geen administraties gevonden." }))] }), effectiveOfficeCode && (_jsxs("div", { className: "flex flex-wrap gap-2 pt-1", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => runSync("customers"), disabled: busy === "customers", children: busy === "customers" ? "Debiteuren…" : "Debiteuren bijwerken" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => runSync("invoices"), disabled: busy === "invoices", children: busy === "invoices" ? "Facturen…" : "Facturen bijwerken" })] }))] }));
156
+ }
157
+ export function TwinfieldAuthSettingsCard({ defaultExpanded = true, } = {}) {
158
+ const startOAuth = useServerFn(startTwinfieldOAuth);
159
+ const syncCentralAccounts = useServerFn(syncTwinfieldCentralAccounts);
160
+ const listConnections = useServerFn(listTwinfieldConnections);
161
+ const getSyncState = useServerFn(getTwinfieldSyncState);
162
+ const fetchPushQueue = useServerFn(listTwinfieldPushQueue);
163
+ const retryQueueItem = useServerFn(retryTwinfieldQueueItem);
164
+ const [connections, setConnections] = useState([]);
165
+ const [loading, setLoading] = useState(true);
166
+ const [checkingCentral, setCheckingCentral] = useState(false);
167
+ const [centralNotice, setCentralNotice] = useState(null);
168
+ const [connecting, setConnecting] = useState(false);
169
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
170
+ const [syncStates, setSyncStates] = useState([]);
171
+ const [counts, setCounts] = useState({
172
+ customers: 0,
173
+ invoices: 0,
174
+ });
175
+ const [pushQueue, setPushQueue] = useState([]);
176
+ const [retrying, setRetrying] = useState(null);
177
+ const loadAll = async (opts = {}) => {
178
+ setLoading(true);
179
+ if (!opts.skipCentralSync)
180
+ setCheckingCentral(true);
181
+ setCentralNotice(null);
182
+ let syncResult = null;
213
183
  try {
214
- await setEnv({ data: { environment: newEnv } });
215
- await loadStatus();
184
+ if (!opts.skipCentralSync) {
185
+ try {
186
+ syncResult = await syncCentralAccounts({ data: undefined });
187
+ }
188
+ catch (e) {
189
+ const message = e?.message ?? "Central Auth synchronisatie mislukt.";
190
+ setCentralNotice(message);
191
+ if (opts.afterOAuthReturn)
192
+ toast.error(message);
193
+ }
194
+ }
195
+ const [connRes, stateRes, queueRes] = await Promise.all([
196
+ listConnections(),
197
+ getSyncState().catch(() => null),
198
+ fetchPushQueue().catch(() => null),
199
+ ]);
200
+ const list = Array.isArray(connRes?.connections) ? connRes.connections : [];
201
+ setConnections(list);
202
+ if (opts.afterOAuthReturn) {
203
+ if (list.length > 0) {
204
+ toast.success("Twinfield-koppeling bijgewerkt.");
205
+ }
206
+ else if (syncResult && (syncResult.received ?? 0) === 0) {
207
+ setCentralNotice("Twinfield heeft toestemming geregistreerd, maar Central Auth geeft nog geen account terug aan deze module.");
208
+ }
209
+ clearTwinfieldReturnParams();
210
+ }
211
+ else if (syncResult && syncResult.ok === false && syncResult.error) {
212
+ setCentralNotice(syncResult.error);
213
+ }
214
+ if (stateRes) {
215
+ setSyncStates(Array.isArray(stateRes.states) ? stateRes.states : []);
216
+ setCounts(stateRes.counts ?? { customers: 0, invoices: 0 });
217
+ }
218
+ if (queueRes) {
219
+ setPushQueue(queueRes.items ?? []);
220
+ }
216
221
  }
217
222
  catch {
218
- toast.error("Omgeving wijzigen vereist admin-rechten.");
223
+ toast.error("Kon Twinfield-status niet laden.");
224
+ }
225
+ finally {
226
+ setCheckingCentral(false);
227
+ setLoading(false);
219
228
  }
220
229
  };
221
- const handleDelete = () => {
222
- setClientId("");
223
- setClientSecret("");
224
- toast.message("Vul nieuwe verbindingsgegevens in en klik op opslaan om te vervangen.");
225
- };
226
- const runSync = async (entity) => {
227
- setBusy(entity);
230
+ useEffect(() => {
231
+ loadAll({ afterOAuthReturn: hasTwinfieldReturnParams() });
232
+ // Achtergrond-polling elke 30s zonder spinner, zodat de UI live blijft.
233
+ const iv = setInterval(() => {
234
+ loadAll({ skipCentralSync: true }).catch(() => { });
235
+ }, 30000);
236
+ return () => clearInterval(iv);
237
+ // eslint-disable-next-line react-hooks/exhaustive-deps
238
+ }, []);
239
+ const handleConnect = async () => {
240
+ setConnecting(true);
228
241
  try {
229
- const res = await trigger({ data: { entity_type: entity } });
230
- if (res.ok)
231
- toast.success(`${entity === "customers" ? "Debiteuren" : "Verkoopfacturen"} bijgewerkt (${res.upserted} rij(en)).`);
232
- else
233
- toast.error(`Bijwerken mislukt: ${res.error ?? "onbekende fout"}`);
234
- await loadSyncState();
242
+ const res = await startOAuth({ data: undefined });
243
+ if (!res?.url)
244
+ throw new Error("Kon geen koppelingsverzoek starten.");
245
+ // Same-tab redirect: Twinfield stuurt via callback terug naar de app
246
+ window.location.href = res.url;
235
247
  }
236
248
  catch (e) {
237
- const msg = e?.message;
238
- toast.error(msg && msg.length < 120 ? msg : "Bijwerken mislukt.");
239
- }
240
- finally {
241
- setBusy(null);
249
+ toast.error(e?.message ?? "Koppelen niet gelukt.");
250
+ setConnecting(false);
242
251
  }
243
252
  };
244
- const stateFor = (e) => states.find((s) => s.entity_type === e);
245
- const isSandbox = environment === "sandbox";
246
- return (_jsxs(Card, { className: "overflow-hidden", children: [_jsxs("div", { className: cn("flex items-start justify-between gap-4 p-6", isExpanded && "border-b"), children: [_jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-muted p-2", children: _jsx("img", { src: twinfieldLogo, alt: "Twinfield", className: "h-full w-full object-contain" }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Twinfield koppeling" }), _jsx(Badge, { variant: "secondary", className: "font-normal", children: isSandbox ? "Testomgeving" : "Live omgeving" }), _jsxs("span", { className: "inline-flex items-center gap-1.5 text-sm", children: [_jsx("span", { className: `h-2 w-2 rounded-full ${connected ? "bg-green-500" : "bg-muted-foreground/40"}` }), _jsx("span", { className: connected ? "text-foreground" : "text-muted-foreground", children: loading ? "Laden…" : connected ? "Verbonden" : "Niet verbonden" })] })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Verbind je Twinfield-account om automatisch facturen en debiteuren over te nemen." })] })] }), _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-8 p-6", children: [_jsx(Step, { n: 1, title: "Kies omgeving", description: "Kies of je wilt testen of live wilt werken.", children: _jsxs("div", { className: "inline-flex rounded-lg border bg-muted/40 p-1", children: [_jsx("button", { type: "button", onClick: () => handleEnvChange("sandbox"), className: `px-4 py-1.5 text-sm rounded-md transition-colors ${isSandbox
247
- ? "bg-background text-foreground shadow-sm"
248
- : "text-muted-foreground hover:text-foreground"}`, children: "Testomgeving" }), _jsx("button", { type: "button", onClick: () => handleEnvChange("live"), className: `px-4 py-1.5 text-sm rounded-md transition-colors ${!isSandbox
249
- ? "bg-background text-foreground shadow-sm"
250
- : "text-muted-foreground hover:text-foreground"}`, children: "Live omgeving" })] }) }), _jsx(Step, { n: 2, title: "Vul verbindingsgegevens in", description: "", children: _jsxs("div", { className: "rounded-lg border p-5 space-y-5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h4", { className: "font-semibold text-foreground", children: "Verbindingsgegevens" }), hasClientId && (_jsx(Badge, { className: "font-normal", children: "Actief" }))] }), hasClientId && (_jsx("button", { type: "button", onClick: handleDelete, className: "text-sm text-muted-foreground hover:text-foreground", children: "Verwijderen" }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-client-id", children: "Client ID" }), _jsx(Input, { id: "tw-client-id", value: clientId, onChange: (e) => setClientId(e.target.value), placeholder: hasClientId ? "••••••••••••••••" : "", autoComplete: "off" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Te vinden in je Twinfield API-instellingen." }), hasClientId && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-client-secret", children: "Client Secret" }), _jsx(Input, { id: "tw-client-secret", type: "password", value: clientSecret, onChange: (e) => setClientSecret(e.target.value), placeholder: hasSecret ? "••••••••" : "", autoComplete: "new-password" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Houd deze sleutel priv\u00E9." }), hasSecret && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-redirect", children: "Doorverwijs-URL" }), _jsx(Input, { id: "tw-redirect", value: REDIRECT_URI, readOnly: true, className: "font-mono text-xs" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Voeg deze URL toe als toegestane callback in je Twinfield-app." })] }), _jsx("div", { children: _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? "Opslaan…" : "Wijzigingen opslaan" }) })] }) }), _jsx(Step, { n: 3, title: "Test de verbinding", description: "Controleer of je gegevens correct zijn en of de koppeling werkt.", children: _jsx(Button, { variant: "outline", onClick: handleTest, disabled: testing, children: testing ? "Testen…" : "Verbinding testen" }) }), _jsx(Step, { n: 4, title: "Kies administratie", description: "Geef aan uit welke Twinfield-administratie we facturen en debiteuren ophalen.", children: _jsxs("div", { className: "rounded-lg border p-5 space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-admin", children: "Administratiecode" }), offices.length > 0 ? (_jsxs(Select, { value: administrationCode || undefined, onValueChange: setAdministrationCode, children: [_jsx(SelectTrigger, { id: "tw-admin", children: _jsx(SelectValue, { placeholder: offices.length === 0
251
- ? "Geen administraties beschikbaar"
252
- : "Kies een administratie", children: (() => {
253
- const selected = offices.find((o) => o.code === administrationCode);
254
- if (!selected)
255
- return null;
256
- return (_jsxs("span", { className: "flex items-baseline gap-2 truncate", children: [_jsx("span", { className: "truncate", children: selected.name ?? selected.code }), _jsx("span", { className: "text-[11px] text-muted-foreground font-mono", children: selected.code })] }));
257
- })() }) }), _jsx(SelectContent, { children: offices.map((o) => (_jsx(SelectItem, { value: o.code, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { children: o.name ?? o.code }), _jsx("span", { className: "text-[11px] text-muted-foreground font-mono", children: o.code })] }) }, o.code))) })] })) : (_jsx(Input, { id: "tw-admin", value: administrationCode, onChange: (e) => setAdministrationCode(e.target.value), autoComplete: "off", placeholder: "Bijv. 1001" })), _jsx("p", { className: "text-xs text-muted-foreground", children: "De code van de administratie in Twinfield (ook wel \"company\" genoemd)." })] }), _jsx(Button, { onClick: handleSaveAdmin, disabled: savingAdmin, children: savingAdmin ? "Opslaan…" : "Instellingen opslaan" })] }) }), _jsxs(Step, { n: 5, title: "Gegevens up-to-date houden", description: "We halen je Twinfield-gegevens automatisch op en bewaren ze in je werkomgeving. Zo werkt alles effici\u00EBnt & snel.", children: [_jsx("div", { className: "space-y-3", children: ([
258
- {
259
- key: "customers",
260
- label: "Debiteuren",
261
- cadence: "Wordt elk uur automatisch bijgewerkt.",
262
- countLabel: "Aantal debiteuren",
263
- },
264
- {
265
- key: "invoices",
266
- label: "Verkoopfacturen",
267
- cadence: "Wordt elke 15 minuten automatisch bijgewerkt.",
268
- countLabel: "Aantal facturen",
269
- },
270
- ]).map((item) => {
271
- const s = stateFor(item.key);
272
- const count = item.key === "customers" ? counts.customers : counts.invoices;
273
- return (_jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-foreground", children: item.label }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: item.cadence })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => runSync(item.key), disabled: busy === item.key, children: busy === item.key ? "Bezig…" : "Nu bijwerken" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Laatst bijgewerkt" }), _jsx("div", { className: "text-sm font-medium text-foreground mt-0.5", children: relativeTime(s?.last_synced_at ?? null) })] }), _jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: item.countLabel }), _jsx("div", { className: "text-sm font-medium text-foreground mt-0.5", children: count })] })] }), s?.last_error && (() => {
274
- const isRateLimited = /429|rate.?limit|fair.?use|concurren/i.test(s.last_error);
275
- return (_jsxs("div", { className: `rounded-md border px-3 py-2 text-xs ${isRateLimited
276
- ? "border-orange-200 bg-orange-50 text-orange-900"
277
- : "border-amber-200 bg-amber-50 text-amber-900"}`, children: [_jsx("div", { className: "font-medium", children: isRateLimited
278
- ? "Twinfield rate-limit bereikt (Fair Use Policy)"
279
- : "Laatste automatische poging mislukte tijdelijk" }), _jsx("div", { className: `mt-1 ${isRateLimited ? "text-orange-800" : "text-amber-800"}`, children: isRateLimited
280
- ? "Twinfield heeft tijdelijk verzoeken geweigerd wegens te veel verkeer. De sync probeert het automatisch opnieuw op het volgende ingeplande moment."
281
- : "Twinfield reageerde niet correct. De volgende geplande update probeert het automatisch opnieuw — je kunt ook nu zelf op \"Nu bijwerken\" klikken." }), _jsxs("details", { className: "mt-1.5", children: [_jsx("summary", { className: `cursor-pointer ${isRateLimited ? "text-orange-700 hover:text-orange-900" : "text-amber-700 hover:text-amber-900"}`, children: "Technische details" }), _jsx("div", { className: `mt-1 font-mono text-[11px] break-all ${isRateLimited ? "text-orange-900/80" : "text-amber-900/80"}`, children: s.last_error })] })] }));
282
- })()] }, item.key));
283
- }) }), connected && pushQueue.length > 0 && (_jsxs("div", { className: "mt-6 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 facturen (push-wachtrij)" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Laatste 20 facturen die vanuit de facturatiemodule naar Twinfield zijn gestuurd." })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: loadPushQueue, children: "Vernieuwen" })] }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: pushQueue.map((q) => {
284
- const variant = q.status === "done"
285
- ? "default"
286
- : q.status === "dead" || q.status === "error"
287
- ? "destructive"
288
- : "secondary";
289
- 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: q.status }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["poging ", q.attempts, "/5"] }), q.external_id && (_jsxs("span", { className: "text-xs font-mono text-foreground", children: ["#", 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) })] }), (q.status === "error" || q.status === "dead") && (_jsx(Button, { size: "sm", variant: "outline", disabled: retrying === q.id, onClick: async () => {
290
- setRetrying(q.id);
291
- try {
292
- await retryQueueItem({ data: { id: q.id } });
293
- toast.success("Opnieuw in de wachtrij geplaatst.");
294
- await loadPushQueue();
295
- }
296
- catch (e) {
297
- toast.error(e?.message ?? "Opnieuw proberen mislukt");
298
- }
299
- finally {
300
- setRetrying(null);
301
- }
302
- }, children: retrying === q.id ? "Bezig…" : "Opnieuw" }))] }, q.id));
303
- }) })] }))] })] })), isExpanded && (_jsx("div", { className: "border-t px-6 py-4 text-center", children: _jsx("a", { href: "https://accounting.twinfield.com/webservices/documentation/", target: "_blank", rel: "noreferrer", className: "text-sm text-primary hover:underline", children: "Waar vind ik deze gegevens in Twinfield?" }) }))] }));
253
+ const hasAny = connections.length > 0;
254
+ const anyConnected = connections.some((c) => c.connected);
255
+ const statusLoading = loading || checkingCentral;
256
+ const stateFor = (e) => syncStates.find((s) => s.entity_type === e);
257
+ return (_jsxs(Card, { className: "overflow-hidden", children: [_jsxs("div", { className: cn("flex items-start justify-between gap-4 p-6", isExpanded && "border-b"), children: [_jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-muted p-2", children: _jsx("img", { src: twinfieldLogo, alt: "Twinfield", className: "h-full w-full object-contain" }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Twinfield koppeling" }), _jsxs("span", { className: "inline-flex items-center gap-1.5 text-sm", children: [_jsx("span", { className: `h-2 w-2 rounded-full ${anyConnected ? "bg-green-500" : "bg-muted-foreground/40"}` }), _jsx("span", { className: anyConnected ? "text-foreground" : "text-muted-foreground", children: statusLoading
258
+ ? "Koppeling controleren…"
259
+ : anyConnected
260
+ ? "Verbonden"
261
+ : "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: [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"] })] })), 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, { conn: c, onChanged: () => loadAll(), onDisconnected: async () => {
262
+ // Optimistisch: verwijder rij direct uit UI en sla Central-sync over,
263
+ // anders wordt de rij vanuit Central Auth meteen opnieuw opgehaald.
264
+ setConnections((prev) => prev.filter((row) => row.id !== c.id));
265
+ await loadAll({ skipCentralSync: true });
266
+ } }, c.id))) })] })), anyConnected && (_jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-foreground", children: "Automatische sync" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Debiteuren worden elke 15 minuten en verkoopfacturen elke 10 minuten automatisch bijgewerkt. Incomplete records worden elke 5 minuten aangevuld. Deze weergave ververst zichzelf elke 30 seconden." })] }), _jsx("div", { className: "grid grid-cols-2 gap-3", children: ["customers", "invoices"].map((k) => {
267
+ const s = stateFor(k);
268
+ const count = k === "customers" ? counts.customers : counts.invoices;
269
+ return (_jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: k === "customers" ? "Debiteuren" : "Verkoopfacturen" }), _jsxs("div", { className: "text-sm font-medium text-foreground mt-0.5", children: [count, " \u00B7 laatst ", relativeTime(s?.last_synced_at ?? null)] }), s?.last_error && (_jsx("div", { className: "text-[11px] text-amber-700 mt-1 break-all", children: s.last_error }))] }, k));
270
+ }) })] })), 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 facturen (push-wachtrij)" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Laatste 20 facturen die naar Twinfield zijn gestuurd." })] }), _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) => {
271
+ const variant = q.status === "done"
272
+ ? "default"
273
+ : q.status === "dead" || q.status === "error"
274
+ ? "destructive"
275
+ : "secondary";
276
+ 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: q.status }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["poging ", q.attempts, "/5"] }), q.external_id && (_jsxs("span", { className: "text-xs font-mono text-foreground", children: ["#", 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) })] }), (q.status === "error" || q.status === "dead") && (_jsx(Button, { size: "sm", variant: "outline", disabled: retrying === q.id, onClick: async () => {
277
+ setRetrying(q.id);
278
+ try {
279
+ await retryQueueItem({ data: { id: q.id } });
280
+ toast.success("Opnieuw in de wachtrij geplaatst.");
281
+ await loadAll();
282
+ }
283
+ catch (e) {
284
+ toast.error(e?.message ?? "Opnieuw proberen mislukt");
285
+ }
286
+ finally {
287
+ setRetrying(null);
288
+ }
289
+ }, children: retrying === q.id ? "Bezig…" : "Opnieuw" }))] }, q.id));
290
+ }) })] }))] }))] }));
304
291
  }
@@ -4,8 +4,9 @@ export * from './components/TemplatePage';
4
4
  export * from './components/TemplateModulePage';
5
5
  export { TemplateModuleLogo } from './components/TemplateModuleLogo';
6
6
  export { TemplateModulePage } from "./components/TemplateModulePage";
7
- export { downloadTwinfieldInvoicePdf, resendTwinfieldInvoice, sendPaymentReminder, duplicateTwinfieldInvoice, } from "./lib/accounting/twinfield/actions.functions";
7
+ export { downloadTwinfieldInvoicePdf, downloadTwinfieldInvoicePdfsBulk, resendTwinfieldInvoice, sendPaymentReminder, duplicateTwinfieldInvoice, } from "./lib/accounting/twinfield/actions.functions";
8
8
  export { getTwinfieldConformance } from "./lib/accounting/twinfield/conformance.functions";
9
+ export { createTwinfieldCustomer, createTwinfieldInvoice, checkTwinfieldWritePermissions, } from "./lib/accounting/twinfield/writes.functions";
9
10
  export { TwinfieldConformanceCard } from "./components/settings/TwinfieldConformanceCard";
10
11
  export declare const twinfieldAuthenticatieModule: FlowModule;
11
12
  export declare const myModule: FlowModule;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAKrE,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,EACtB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kDAAkD,CAAC;AAC3F,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAE1F,eAAO,MAAM,4BAA4B,EAAE,UAa1C,CAAC;AAEF,eAAO,MAAM,QAAQ,YAA+B,CAAC;AACrD,eAAO,MAAM,4BAA4B,YAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAKrE,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,sBAAsB,EACtB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kDAAkD,CAAC;AAC3F,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAE1F,eAAO,MAAM,4BAA4B,EAAE,UAY1C,CAAC;AAEF,eAAO,MAAM,QAAQ,YAA+B,CAAC;AACrD,eAAO,MAAM,4BAA4B,YAA+B,CAAC"}