@flowselections/floriday-authenticatie-module 1.0.12 → 1.0.13

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 (29) hide show
  1. package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
  2. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts +2 -0
  3. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts.map +1 -0
  4. package/dist-lib/components/floriday/FlorydaySyncJobsCard.js +99 -0
  5. package/dist-lib/components/floriday/TokenHealthCard.d.ts +2 -0
  6. package/dist-lib/components/floriday/TokenHealthCard.d.ts.map +1 -0
  7. package/dist-lib/components/floriday/TokenHealthCard.js +93 -0
  8. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
  9. package/dist-lib/components/settings/FlorydaySettingsCard.js +116 -3
  10. package/dist-lib/index.d.ts +1 -0
  11. package/dist-lib/index.d.ts.map +1 -1
  12. package/dist-lib/index.js +2 -1
  13. package/dist-lib/integrations/supabase/auth-middleware.d.ts +4685 -595
  14. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  15. package/dist-lib/integrations/supabase/client.d.ts +4685 -595
  16. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  17. package/dist-lib/integrations/supabase/client.server.d.ts +4685 -595
  18. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  19. package/dist-lib/integrations/supabase/client.server.js +8 -2
  20. package/dist-lib/integrations/supabase/types.d.ts +4788 -586
  21. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.js +11 -0
  23. package/dist-lib/lib/floriday-sync.functions.d.ts +49839 -0
  24. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -0
  25. package/dist-lib/lib/floriday-sync.functions.js +306 -0
  26. package/dist-lib/lib/floriday.functions.d.ts +82115 -6526
  27. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  28. package/dist-lib/lib/floriday.functions.js +475 -37
  29. package/package.json +5 -4
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "asset_id": "6db44648-6f9b-4335-8b88-714ad7aaee9d",
4
+ "project_id": "27b6733b-2e2f-4518-b204-681958559055",
5
+ "url": "/__l5e/assets-v1/6db44648-6f9b-4335-8b88-714ad7aaee9d/floriday-logo.png",
6
+ "r2_key": "a/v1/27b6733b-2e2f-4518-b204-681958559055/6db44648-6f9b-4335-8b88-714ad7aaee9d/floriday-logo.png",
7
+ "original_filename": "floriday-logo.png",
8
+ "size": 6921,
9
+ "content_type": "image/png",
10
+ "created_at": "2026-06-24T12:45:45Z"
11
+ }
@@ -0,0 +1,2 @@
1
+ export declare function FlorydaySyncJobsCard(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=FlorydaySyncJobsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlorydaySyncJobsCard.d.ts","sourceRoot":"","sources":["../../../src/components/floriday/FlorydaySyncJobsCard.tsx"],"names":[],"mappings":"AAyDA,wBAAgB,oBAAoB,4CAiInC"}
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useServerFn } from "@tanstack/react-start";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { Card, Button, Badge, toast } from "@flowselections/core";
5
+ import { scheduleFlorydayTradeItemsSync, scheduleFlorydaySalesOrdersSync, scheduleFlorydayCustomerOrganizationsSync, scheduleFlorydayCarriersSync, syncFlorydayTradeItems, syncFlorydaySalesOrders, syncFlorydayCustomerOrganizations, syncFlorydayCarriers, } from "../../lib/floriday-sync.functions";
6
+ import { getFlorydayCronStatus, getFlorydaySettings, } from "../../lib/floriday.functions";
7
+ const RESOURCES = [
8
+ {
9
+ key: "trade-items",
10
+ label: "Producten (trade items)",
11
+ schedule: "Elke 5 minuten",
12
+ jobname: "floriday-trade-items-sync-5min",
13
+ },
14
+ {
15
+ key: "sales-orders",
16
+ label: "Verkooporders",
17
+ schedule: "Elke 5 minuten",
18
+ jobname: "floriday-salesorders-sync-5min",
19
+ },
20
+ {
21
+ key: "customer-organizations",
22
+ label: "Klantorganisaties",
23
+ schedule: "Dagelijks 06:00 & 18:00 UTC",
24
+ jobname: "floriday-customer-organizations-sync-twice-daily",
25
+ },
26
+ {
27
+ key: "carriers",
28
+ label: "Vervoerders",
29
+ schedule: "Dagelijks 06:00 & 18:00 UTC",
30
+ jobname: "floriday-carriers-sync-twice-daily",
31
+ },
32
+ ];
33
+ export function FlorydaySyncJobsCard() {
34
+ const qc = useQueryClient();
35
+ const status = useServerFn(getFlorydayCronStatus);
36
+ const settings = useServerFn(getFlorydaySettings);
37
+ const scheduleTrade = useServerFn(scheduleFlorydayTradeItemsSync);
38
+ const scheduleSales = useServerFn(scheduleFlorydaySalesOrdersSync);
39
+ const scheduleCustomers = useServerFn(scheduleFlorydayCustomerOrganizationsSync);
40
+ const scheduleCarriers = useServerFn(scheduleFlorydayCarriersSync);
41
+ const syncTrade = useServerFn(syncFlorydayTradeItems);
42
+ const syncSales = useServerFn(syncFlorydaySalesOrders);
43
+ const syncCustomers = useServerFn(syncFlorydayCustomerOrganizations);
44
+ const syncCarriers = useServerFn(syncFlorydayCarriers);
45
+ const statusQuery = useQuery({
46
+ queryKey: ["floriday-cron-status"],
47
+ queryFn: () => status(),
48
+ });
49
+ const settingsQuery = useQuery({
50
+ queryKey: ["floriday-settings"],
51
+ queryFn: () => settings(),
52
+ });
53
+ const env = settingsQuery.data?.active_environment ?? "staging";
54
+ const jobs = statusQuery.data?.ok ? statusQuery.data.jobs : [];
55
+ const scheduleFor = (key) => {
56
+ if (key === "trade-items")
57
+ return scheduleTrade;
58
+ if (key === "sales-orders")
59
+ return scheduleSales;
60
+ if (key === "carriers")
61
+ return scheduleCarriers;
62
+ return scheduleCustomers;
63
+ };
64
+ const syncFor = (key) => {
65
+ if (key === "trade-items")
66
+ return syncTrade;
67
+ if (key === "sales-orders")
68
+ return syncSales;
69
+ if (key === "carriers")
70
+ return syncCarriers;
71
+ return syncCustomers;
72
+ };
73
+ const scheduleMutation = useMutation({
74
+ mutationFn: async (key) => scheduleFor(key)(),
75
+ onSuccess: (res) => {
76
+ toast.success(`Cron ingepland: ${res.jobname}`);
77
+ qc.invalidateQueries({ queryKey: ["floriday-cron-status"] });
78
+ },
79
+ onError: (e) => toast.error(e?.message ?? "Inplannen mislukt"),
80
+ });
81
+ const syncNowMutation = useMutation({
82
+ mutationFn: async (key) => syncFor(key)({ data: { environment: env } }),
83
+ onSuccess: (res) => {
84
+ if (res?.ok) {
85
+ toast.success(`${res.resource}: ${res.synced} item(s) gesynchroniseerd (${res.fromSequence} → ${res.toSequence})`);
86
+ }
87
+ else {
88
+ toast.error(`${res?.resource ?? "sync"} mislukt: ${res?.error ?? "onbekende fout"}`);
89
+ }
90
+ },
91
+ onError: (e) => toast.error(e?.message ?? "Sync mislukt"),
92
+ });
93
+ return (_jsxs(Card, { className: "p-5 space-y-4", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-base font-semibold", children: "Geplande synchronisaties" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Onze servers halen je Floriday-gegevens automatisch op via deze schema's. Je kunt ze ook handmatig bijwerken." })] }), _jsx("div", { className: "space-y-3", children: RESOURCES.map((r) => {
94
+ const job = jobs.find((j) => j.jobname === r.jobname);
95
+ const isScheduling = scheduleMutation.isPending && scheduleMutation.variables === r.key;
96
+ const isSyncing = syncNowMutation.isPending && syncNowMutation.variables === r.key;
97
+ return (_jsxs("div", { className: "flex items-start justify-between gap-3 rounded-md border border-border/60 bg-card p-4", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("h3", { className: "text-sm font-medium", children: r.label }), job?.active ? (_jsx(Badge, { variant: "default", className: "text-[10px]", children: "Actief" })) : (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: "Niet ingepland" }))] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: r.schedule }), job?.url && (_jsx("p", { className: "text-[11px] text-muted-foreground mt-1 truncate font-mono", children: job.url }))] }), _jsxs("div", { className: "flex flex-col gap-2 shrink-0", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => scheduleMutation.mutate(r.key), disabled: isScheduling, children: isScheduling ? "Inplannen…" : job ? "Herplannen" : "Inplannen" }), _jsx(Button, { size: "sm", onClick: () => syncNowMutation.mutate(r.key), disabled: isSyncing, children: isSyncing ? "Synchroniseren…" : "Nu synchroniseren" })] })] }, r.key));
98
+ }) })] }));
99
+ }
@@ -0,0 +1,2 @@
1
+ export declare function TokenHealthCard(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=TokenHealthCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenHealthCard.d.ts","sourceRoot":"","sources":["../../../src/components/floriday/TokenHealthCard.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,eAAe,4CA4K9B"}
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { useServerFn } from "@tanstack/react-start";
4
+ import { useState } from "react";
5
+ import { getFlorydayTokenHealth, forceRefreshFlorydayToken, getFlorydayCronStatus, reinstallFlorydayCrons, } from "../../lib/floriday.functions";
6
+ function formatRelative(iso) {
7
+ if (!iso)
8
+ return { label: "geen token", expired: true };
9
+ const target = Date.parse(iso);
10
+ const diffMs = target - Date.now();
11
+ const absMins = Math.round(Math.abs(diffMs) / 60000);
12
+ const human = absMins < 60
13
+ ? `${absMins} min`
14
+ : absMins < 60 * 48
15
+ ? `${Math.round(absMins / 60)} uur`
16
+ : new Date(iso).toLocaleString("nl-NL");
17
+ if (diffMs < 0)
18
+ return { label: `verlopen sinds ${human}`, expired: true };
19
+ return { label: `verloopt over ${human}`, expired: false };
20
+ }
21
+ function statusColor(expiresAt) {
22
+ if (!expiresAt)
23
+ return "bg-red-500";
24
+ const mins = (Date.parse(expiresAt) - Date.now()) / 60000;
25
+ if (mins < 5)
26
+ return "bg-red-500";
27
+ if (mins < 30)
28
+ return "bg-amber-500";
29
+ return "bg-emerald-500";
30
+ }
31
+ function shortUrl(u) {
32
+ if (!u)
33
+ return "—";
34
+ try {
35
+ const url = new URL(u);
36
+ return `${url.host}${url.pathname}`;
37
+ }
38
+ catch {
39
+ return u;
40
+ }
41
+ }
42
+ export function TokenHealthCard() {
43
+ const qc = useQueryClient();
44
+ const fetchHealth = useServerFn(getFlorydayTokenHealth);
45
+ const forceRefresh = useServerFn(forceRefreshFlorydayToken);
46
+ const fetchCronStatus = useServerFn(getFlorydayCronStatus);
47
+ const reinstall = useServerFn(reinstallFlorydayCrons);
48
+ const [busyEnv, setBusyEnv] = useState(null);
49
+ const { data, isLoading, refetch } = useQuery({
50
+ queryKey: ["floriday-token-health"],
51
+ queryFn: () => fetchHealth(),
52
+ refetchInterval: 60000,
53
+ });
54
+ const cronStatus = useQuery({
55
+ queryKey: ["floriday-cron-status"],
56
+ queryFn: () => fetchCronStatus(),
57
+ refetchInterval: 120000,
58
+ });
59
+ const refresh = useMutation({
60
+ mutationFn: (environment) => {
61
+ setBusyEnv(environment);
62
+ return forceRefresh({ data: { environment } });
63
+ },
64
+ onSettled: () => {
65
+ setBusyEnv(null);
66
+ refetch();
67
+ },
68
+ });
69
+ const reinstallMutation = useMutation({
70
+ mutationFn: () => reinstall(),
71
+ onSettled: () => {
72
+ qc.invalidateQueries({ queryKey: ["floriday-cron-status"] });
73
+ qc.invalidateQueries({ queryKey: ["floriday-token-health"] });
74
+ },
75
+ });
76
+ if (isLoading) {
77
+ return (_jsx("div", { className: "rounded-lg border p-4 text-sm text-muted-foreground", children: "Token gezondheid laden\u2026" }));
78
+ }
79
+ const conns = data?.connections ?? [];
80
+ const recent = data?.recent ?? [];
81
+ const lastOk = recent.find((r) => r.ok);
82
+ const expectedBase = cronStatus.data?.expectedBase;
83
+ const jobs = (cronStatus.data?.jobs ?? []);
84
+ const tokenJob = jobs.find((j) => j.jobname === "refresh-floriday-tokens-hourly");
85
+ const cronUrlMismatch = Boolean(expectedBase && tokenJob?.url && !tokenJob.url.startsWith(expectedBase));
86
+ return (_jsxs("div", { className: "rounded-lg border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold", children: "Token gezondheid" }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "De achtergrond-cron vernieuwt tokens elke 15 minuten. Bij elke API-aanroep wordt bovendien automatisch geretried met een vers token bij een 401." })] }), lastOk && (_jsxs("span", { className: "text-xs text-muted-foreground shrink-0", children: ["Laatste succesvolle refresh:", " ", new Date(lastOk.attempted_at).toLocaleString("nl-NL")] }))] }), conns.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen actieve koppelingen." })) : (_jsx("ul", { className: "space-y-2", children: conns.map((c) => {
87
+ const rel = formatRelative(c.token_expires_at);
88
+ return (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: `inline-block h-2.5 w-2.5 rounded-full shrink-0 ${statusColor(c.token_expires_at)}` }), _jsx("span", { className: "font-medium capitalize", children: c.environment }), _jsx("span", { className: rel.expired ? "text-red-600" : "text-muted-foreground", children: rel.label })] }), _jsx("button", { type: "button", onClick: () => refresh.mutate(c.environment), disabled: busyEnv === c.environment, className: "rounded-md border px-2.5 py-1 text-xs hover:bg-muted disabled:opacity-50 shrink-0", children: busyEnv === c.environment ? "Vernieuwen…" : "Nu vernieuwen" })] }, c.id));
89
+ }) })), _jsxs("div", { className: "border-t pt-3 space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("h3", { className: "text-sm font-medium", children: "Geplande cron-taken" }), _jsx("button", { type: "button", onClick: () => reinstallMutation.mutate(), disabled: reinstallMutation.isPending, className: "rounded-md border px-2.5 py-1 text-xs hover:bg-muted disabled:opacity-50", children: reinstallMutation.isPending ? "Installeren…" : "Cron opnieuw installeren" })] }), cronStatus.isLoading ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "Status laden\u2026" })) : jobs.length === 0 ? (_jsx("p", { className: "text-xs text-amber-600", children: "Geen Floriday-cron jobs gevonden. Klik \"Cron opnieuw installeren\" om ze in te plannen." })) : (_jsx("ul", { className: "space-y-1 text-xs", children: jobs.map((j) => {
90
+ const ok = expectedBase ? j.url?.startsWith(expectedBase) : true;
91
+ return (_jsxs("li", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: j.jobname }), _jsxs("div", { className: "text-muted-foreground", children: [j.schedule, " \u00B7 ", _jsx("span", { className: ok ? "" : "text-red-600", children: shortUrl(j.url) })] })] }), _jsx("span", { className: `inline-block h-2 w-2 rounded-full shrink-0 ${j.active && ok ? "bg-emerald-500" : "bg-red-500"}`, "aria-label": j.active && ok ? "actief" : "fout" })] }, j.jobname));
92
+ }) })), cronUrlMismatch && (_jsx("p", { className: "text-xs text-red-600", children: "Cron-URL wijst niet naar deze app. Klik op \"Cron opnieuw installeren\" om dit te herstellen." })), expectedBase && (_jsxs("p", { className: "text-[11px] text-muted-foreground", children: ["Verwachte base-URL: ", _jsx("code", { className: "font-mono", children: expectedBase })] }))] }), recent.some((r) => !r.ok) && (_jsxs("details", { className: "text-xs", children: [_jsxs("summary", { className: "cursor-pointer text-muted-foreground", children: ["Recente fouten (", recent.filter((r) => !r.ok).length, ")"] }), _jsx("ul", { className: "mt-2 space-y-1", children: recent.filter((r) => !r.ok).slice(0, 5).map((r, i) => (_jsxs("li", { className: "text-red-600", children: [new Date(r.attempted_at).toLocaleString("nl-NL"), " \u2014 ", r.error] }, i))) })] }))] }));
93
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"FlorydaySettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/FlorydaySettingsCard.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,oBAAoB,4CAkHnC"}
1
+ {"version":3,"file":"FlorydaySettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/FlorydaySettingsCard.tsx"],"names":[],"mappings":"AAuDA,wBAAgB,oBAAoB,4CAqHnC"}
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
+ import florydayLogo from "../../assets/floriday-logo.png.asset.json";
3
4
  import { ChevronDown } from "lucide-react";
4
5
  import { useServerFn } from "@tanstack/react-start";
5
6
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
6
7
  import { Card, Button, Input, Label, Badge, Tabs, TabsList, TabsTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, toast, } from "@flowselections/core";
7
- import { listFlorydayConnections, upsertFlorydayConnection, testFlorydayConnection, deleteFlorydayConnection, getFlorydaySettings, setFlorydayActiveEnvironment, listFlorydayWarehouses, updateFlorydayPublishingSettings, validateGln, } from "../../lib/floriday.functions";
8
+ import { listFlorydayConnections, upsertFlorydayConnection, testFlorydayConnection, deleteFlorydayConnection, getFlorydaySettings, setFlorydayActiveEnvironment, listFlorydayWarehouses, updateFlorydayPublishingSettings, validateGln, syncFlorydayConnections, getFlorydaySyncStatus, } from "../../lib/floriday.functions";
9
+ import { syncFlorydayTradeItems, syncFlorydaySalesOrders, syncFlorydayCustomerOrganizations, syncFlorydayCarriers, } from "../../lib/floriday-sync.functions";
8
10
  export function FlorydaySettingsCard() {
9
11
  const [open, setOpen] = useState(false);
10
12
  const qc = useQueryClient();
@@ -39,7 +41,7 @@ export function FlorydaySettingsCard() {
39
41
  toast.success(`Gebruik modus: ${env === "live" ? "Live omgeving" : "Testomgeving"}`);
40
42
  },
41
43
  });
42
- return (_jsxs(Card, { className: "overflow-hidden border-border/60", children: [_jsxs("button", { type: "button", onClick: () => setOpen(o => !o), className: "w-full flex items-center gap-3 px-5 py-4 text-left hover:bg-muted/40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", "aria-expanded": open, children: [_jsx("div", { className: "h-9 w-9 rounded-lg bg-muted/60 flex items-center justify-center shrink-0", children: _jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-green-600 text-xs font-bold text-white", children: "F" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h3", { className: "font-semibold text-sm truncate", children: "Floriday koppeling" }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap mt-1", children: [_jsx(Badge, { variant: activeEnv === "live" ? "default" : "secondary", className: "text-[10px] tracking-wide", children: activeEnv === "live" ? "Live omgeving" : "Testomgeving" }), _jsx(StatusIndicator, { connected: Boolean(activeConn) }), activeConn?.organization_name && (_jsxs("span", { className: "text-xs text-muted-foreground truncate", children: ["\u00B7 ", activeConn.organization_name] }))] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: "Verbind je Floriday-account om automatisch gegevens uit te wisselen met het platform." })] }), _jsx(ChevronDown, { className: `h-4 w-4 text-muted-foreground shrink-0 transition-transform ${open ? "rotate-180" : ""}` })] }), open && (_jsxs("div", { className: "border-t border-border/60 px-5 py-5 space-y-6 bg-muted/20", children: [_jsx(StepBlock, { number: 1, title: "Kies omgeving", description: "Kies of je wilt testen of live wilt werken.", children: _jsx(Tabs, { value: activeEnv, onValueChange: (v) => envMutation.mutate(v), children: _jsxs(TabsList, { className: "grid grid-cols-2 w-full max-w-xs", children: [_jsx(TabsTrigger, { value: "staging", disabled: envMutation.isPending, children: "Testomgeving" }), _jsx(TabsTrigger, { value: "live", disabled: envMutation.isPending, children: "Live omgeving" })] }) }) }), isLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(EnvironmentForm, { env: activeEnv, label: activeEnv === "live" ? "Live omgeving" : "Testomgeving", connection: activeConn }, activeEnv)), _jsx("div", { className: "pt-2 border-t border-border/60", children: _jsx("a", { href: "https://developer.floriday.io/docs", target: "_blank", rel: "noopener noreferrer", className: "text-xs text-primary hover:underline inline-flex items-center gap-1", children: "Waar vind ik deze gegevens in Floriday?" }) })] }))] }));
44
+ return (_jsxs(Card, { className: "overflow-hidden border-border/60", children: [_jsxs("button", { type: "button", onClick: () => setOpen(o => !o), className: "w-full flex items-center gap-3 px-5 py-4 text-left hover:bg-muted/40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", "aria-expanded": open, children: [_jsx("div", { className: "h-9 w-9 rounded-lg bg-muted/60 flex items-center justify-center shrink-0", children: _jsx("img", { src: florydayLogo.url, alt: "Floriday", className: "h-6 w-6 object-contain" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h3", { className: "font-semibold text-sm truncate", children: "Floriday koppeling" }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap mt-1", children: [_jsx(Badge, { variant: activeEnv === "live" ? "default" : "secondary", className: "text-[10px] tracking-wide", children: activeEnv === "live" ? "Live omgeving" : "Testomgeving" }), _jsx(StatusIndicator, { connected: Boolean(activeConn) }), activeConn?.organization_name && (_jsxs("span", { className: "text-xs text-muted-foreground truncate", children: ["\u00B7 ", activeConn.organization_name] }))] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: "Verbind je Floriday-account om automatisch gegevens uit te wisselen met het platform." })] }), _jsx(ChevronDown, { className: `h-4 w-4 text-muted-foreground shrink-0 transition-transform ${open ? "rotate-180" : ""}` })] }), open && (_jsxs("div", { className: "border-t border-border/60 px-5 py-5 space-y-6 bg-muted/20", children: [_jsx(StepBlock, { number: 1, title: "Kies omgeving", description: "Kies of je wilt testen of live wilt werken.", children: _jsx(Tabs, { value: activeEnv, onValueChange: (v) => envMutation.mutate(v), children: _jsxs(TabsList, { className: "grid grid-cols-2 w-full max-w-xs", children: [_jsx(TabsTrigger, { value: "staging", disabled: envMutation.isPending, children: "Testomgeving" }), _jsx(TabsTrigger, { value: "live", disabled: envMutation.isPending, children: "Live omgeving" })] }) }) }), isLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden\u2026" })) : (_jsx(EnvironmentForm, { env: activeEnv, label: activeEnv === "live" ? "Live omgeving" : "Testomgeving", connection: activeConn }, activeEnv)), _jsx("div", { className: "pt-2 border-t border-border/60", children: _jsx("a", { href: "https://developer.floriday.io/docs", target: "_blank", rel: "noopener noreferrer", className: "text-xs text-primary hover:underline inline-flex items-center gap-1", children: "Waar vind ik deze gegevens in Floriday?" }) })] }))] }));
43
45
  }
44
46
  function StatusIndicator({ connected }) {
45
47
  const label = connected ? "Verbonden" : "Niet verbonden";
@@ -97,7 +99,7 @@ function EnvironmentForm({ env, label, connection, }) {
97
99
  onError: (e) => toast.error(e?.message ?? "Verwijderen mislukt"),
98
100
  });
99
101
  return (_jsxs("div", { className: "space-y-6", children: [_jsx(StepBlock, { number: 2, title: "Vul verbindingsgegevens in", children: _jsxs("div", { className: "rounded-lg border border-border/60 bg-card p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("h4", { className: "font-medium text-sm", children: "Verbindingsgegevens" }), connection ? (_jsx(Badge, { variant: "default", className: "text-[10px]", children: "Actief" })) : (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: "Nog niet gekoppeld" }))] }), connection && (_jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", disabled: deleteMutation.isPending, children: "Verwijderen" }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Koppeling verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Hiermee worden de opgeslagen Floriday-gegevens voor de ", label.toLowerCase(), " permanent verwijderd. Andere modules die deze koppeling gebruiken werken daarna niet meer totdat je opnieuw koppelt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => deleteMutation.mutate(), children: "Verwijderen" })] })] })] }))] }), _jsxs("div", { className: "grid gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${env}-client-id`, className: "text-xs", children: "Client ID" }), _jsx(Input, { id: `${env}-client-id`, value: clientId, onChange: e => setClientId(e.target.value), placeholder: connection ? connection.client_id_masked : "Floriday Client ID", className: connection ? "font-mono placeholder:font-mono placeholder:text-foreground/60" : undefined, autoComplete: "off" }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: "Te vinden in je Floriday API-instellingen." }), connection && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${env}-client-secret`, className: "text-xs", children: "Client Secret" }), _jsx(Input, { id: `${env}-client-secret`, type: "password", value: clientSecret, onChange: e => setClientSecret(e.target.value), placeholder: connection ? "••••••••" : "Floriday Client Secret", className: connection ? "placeholder:text-foreground/60" : undefined, autoComplete: "off" }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: "Houd deze sleutel priv\u00E9." }), connection && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${env}-api-key`, className: "text-xs", children: "API-sleutel" }), _jsx(Input, { id: `${env}-api-key`, type: "text", value: apiKey, onChange: e => setApiKey(e.target.value), placeholder: connection ? connection.api_key_masked : "X-Api-Key uit Floriday", className: connection ? "font-mono placeholder:font-mono placeholder:text-foreground/60" : undefined, autoComplete: "off" }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: "Kopieer de API-sleutel uit Floriday." }), connection && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] })] }), _jsx("div", { children: _jsx(Button, { size: "sm", onClick: () => saveMutation.mutate(), disabled: saveMutation.isPending ||
100
- !clientId.trim() || !clientSecret.trim() || !apiKey.trim(), children: saveMutation.isPending ? "Opslaan…" : connection ? "Wijzigingen opslaan" : "Koppeling opslaan" }) })] }) }), _jsx(StepBlock, { number: 3, title: "Test de verbinding", description: "Controleer of je gegevens correct zijn en of de koppeling werkt.", children: _jsx(Button, { variant: "outline", size: "sm", onClick: () => testMutation.mutate(), disabled: !connection || testMutation.isPending, children: testMutation.isPending ? "Testen…" : "Verbinding testen" }) }), _jsx(StepBlock, { number: 4, title: "Publicatie-instellingen", description: "Vul je GLN-code in en kies het voorkeursmagazijn voor het publiceren van producten via Floriday.", children: _jsx(PublishingSettings, { env: env, connection: connection }) })] }));
102
+ !clientId.trim() || !clientSecret.trim() || !apiKey.trim(), children: saveMutation.isPending ? "Opslaan…" : connection ? "Wijzigingen opslaan" : "Koppeling opslaan" }) })] }) }), _jsx(StepBlock, { number: 3, title: "Test de verbinding", description: "Controleer of je gegevens correct zijn en of de koppeling werkt.", children: _jsx(Button, { variant: "outline", size: "sm", onClick: () => testMutation.mutate(), disabled: !connection || testMutation.isPending, children: testMutation.isPending ? "Testen…" : "Verbinding testen" }) }), _jsx(StepBlock, { number: 4, title: "Publicatie-instellingen", description: "Vul je GLN-code in en kies het voorkeursmagazijn voor het publiceren van producten via Floriday.", children: _jsx(PublishingSettings, { env: env, connection: connection }) }), _jsx(StepBlock, { number: 5, title: "Gegevens up-to-date houden", description: "We halen je Floriday-gegevens automatisch op en bewaren ze in je werkomgeving. Zo werkt alles effici\u00EBnt & snel.", children: _jsx(SyncStatusPanel, { env: env, connection: connection }) })] }));
101
103
  }
102
104
  function PublishingSettings({ env, connection, }) {
103
105
  const qc = useQueryClient();
@@ -161,3 +163,114 @@ function PublishingSettings({ env, connection, }) {
161
163
  Boolean(glnError) ||
162
164
  (!gln.trim() && !warehouseId), children: saveMutation.isPending ? "Opslaan…" : "Instellingen opslaan" }) })] }));
163
165
  }
166
+ function formatRelative(iso) {
167
+ if (!iso)
168
+ return "nog niet gesynchroniseerd";
169
+ const diffMs = Date.now() - Date.parse(iso);
170
+ if (Number.isNaN(diffMs))
171
+ return iso;
172
+ const mins = Math.round(diffMs / 60000);
173
+ if (mins < 1)
174
+ return "zojuist";
175
+ if (mins < 60)
176
+ return `${mins} min geleden`;
177
+ const hrs = Math.round(mins / 60);
178
+ if (hrs < 24)
179
+ return `${hrs} uur geleden`;
180
+ const days = Math.round(hrs / 24);
181
+ return `${days} dag${days === 1 ? "" : "en"} geleden`;
182
+ }
183
+ const SYNC_RESOURCES = [
184
+ {
185
+ key: "connections",
186
+ title: "Inkooprelaties",
187
+ schedule: "Wordt twee keer per dag automatisch bijgewerkt (rond 08:00 en 20:00).",
188
+ countLabel: "Aantal relaties",
189
+ successLabel: "Inkooprelaties",
190
+ },
191
+ {
192
+ key: "trade-items",
193
+ title: "Producten",
194
+ schedule: "Wordt elke 5 minuten automatisch bijgewerkt.",
195
+ countLabel: "Aantal producten",
196
+ successLabel: "Producten",
197
+ },
198
+ {
199
+ key: "sales-orders",
200
+ title: "Verkooporders",
201
+ schedule: "Wordt elke 5 minuten automatisch bijgewerkt.",
202
+ countLabel: "Aantal orders",
203
+ successLabel: "Verkooporders",
204
+ },
205
+ {
206
+ key: "customer-organizations",
207
+ title: "Klantorganisaties",
208
+ schedule: "Wordt twee keer per dag automatisch bijgewerkt (rond 08:00 en 20:00).",
209
+ countLabel: "Aantal klanten",
210
+ successLabel: "Klantorganisaties",
211
+ },
212
+ {
213
+ key: "carriers",
214
+ title: "Vervoerders",
215
+ schedule: "Wordt twee keer per dag automatisch bijgewerkt (rond 08:00 en 20:00).",
216
+ countLabel: "Aantal vervoerders",
217
+ successLabel: "Vervoerders",
218
+ },
219
+ ];
220
+ function SyncStatusPanel({ env, connection, }) {
221
+ const qc = useQueryClient();
222
+ const getStatus = useServerFn(getFlorydaySyncStatus);
223
+ const syncConnections = useServerFn(syncFlorydayConnections);
224
+ const syncTrade = useServerFn(syncFlorydayTradeItems);
225
+ const syncSales = useServerFn(syncFlorydaySalesOrders);
226
+ const syncCustomers = useServerFn(syncFlorydayCustomerOrganizations);
227
+ const syncCarriers = useServerFn(syncFlorydayCarriers);
228
+ const statusQuery = useQuery({
229
+ queryKey: ["floriday-sync-status", env],
230
+ queryFn: () => getStatus({ data: { environment: env } }),
231
+ enabled: Boolean(connection),
232
+ refetchInterval: 30000,
233
+ });
234
+ const syncFor = (key) => {
235
+ if (key === "connections")
236
+ return () => syncConnections({ data: { environment: env } });
237
+ if (key === "trade-items")
238
+ return () => syncTrade({ data: { environment: env } });
239
+ if (key === "sales-orders")
240
+ return () => syncSales({ data: { environment: env } });
241
+ if (key === "carriers")
242
+ return () => syncCarriers({ data: { environment: env } });
243
+ return () => syncCustomers({ data: { environment: env } });
244
+ };
245
+ const syncMutation = useMutation({
246
+ mutationFn: async (key) => {
247
+ const res = await syncFor(key)();
248
+ return { key, res };
249
+ },
250
+ onSuccess: ({ key, res }) => {
251
+ const meta = SYNC_RESOURCES.find((r) => r.key === key);
252
+ if (res?.ok) {
253
+ const n = res.synced ?? 0;
254
+ toast.success(n > 0
255
+ ? `${meta.successLabel} bijgewerkt — ${n} wijziging${n === 1 ? "" : "en"} opgehaald.`
256
+ : `${meta.successLabel}: alles is al up-to-date.`);
257
+ }
258
+ else {
259
+ toast.error(`Bijwerken mislukt: ${res?.error ?? "onbekende fout"}`);
260
+ }
261
+ qc.invalidateQueries({ queryKey: ["floriday-sync-status", env] });
262
+ },
263
+ onError: (e) => toast.error(e?.message ?? "Bijwerken mislukt"),
264
+ });
265
+ if (!connection) {
266
+ return (_jsx("p", { className: "text-xs text-muted-foreground", children: "Sla eerst je verbindingsgegevens op om je gegevens automatisch bij te laten werken." }));
267
+ }
268
+ const statuses = statusQuery.data?.ok && Array.isArray(statusQuery.data.statuses)
269
+ ? statusQuery.data.statuses
270
+ : [];
271
+ return (_jsxs("div", { className: "space-y-3", children: [SYNC_RESOURCES.map((meta) => {
272
+ const status = statuses.find((s) => s.entity_type === meta.key) ?? null;
273
+ const isPending = syncMutation.isPending && syncMutation.variables === meta.key;
274
+ return (_jsxs("div", { className: "rounded-lg border border-border/60 bg-card p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium", children: meta.title }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: meta.schedule })] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => syncMutation.mutate(meta.key), disabled: isPending, children: isPending ? "Bijwerken…" : "Nu bijwerken" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-[11px]", children: [_jsxs("div", { className: "rounded border border-border/40 bg-muted/30 px-2 py-1.5", children: [_jsx("div", { className: "text-muted-foreground", children: "Laatst bijgewerkt" }), _jsx("div", { className: "font-medium text-foreground", children: formatRelative(status?.last_synced_at ?? null) })] }), _jsxs("div", { className: "rounded border border-border/40 bg-muted/30 px-2 py-1.5", children: [_jsx("div", { className: "text-muted-foreground", children: meta.countLabel }), _jsx("div", { className: "font-medium text-foreground", children: status?.cached_count ?? 0 })] })] }), status?.last_error && (_jsxs("p", { className: "text-[11px] text-red-600", children: ["Laatste fout: ", status.last_error] }))] }, meta.key));
275
+ }), _jsx("p", { className: "text-[11px] text-muted-foreground pt-1", children: "We slaan deze gegevens veilig op in je werkomgeving, zodat alles snel laadt en je niet hoeft te wachten op Floriday." })] }));
276
+ }
@@ -2,5 +2,6 @@ import type { FlowModule } from "@flowselections/core";
2
2
  export * from './_core-safelist';
3
3
  export { FlorydayConnectionPage } from './components/floriday/FlorydayConnectionPage';
4
4
  export * from './lib/floriday.functions';
5
+ export * from './lib/floriday-sync.functions';
5
6
  export declare const floridayAuthenticatieModule: FlowModule;
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AACtF,cAAc,0BAA0B,CAAC;AAEzC,eAAO,MAAM,2BAA2B,EAAE,UAczC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AACtF,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAE9C,eAAO,MAAM,2BAA2B,EAAE,UAczC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -3,13 +3,14 @@ import { FlorydaySettingsCard } from "./components/settings/FlorydaySettingsCard
3
3
  export * from './_core-safelist';
4
4
  export { FlorydayConnectionPage } from './components/floriday/FlorydayConnectionPage';
5
5
  export * from './lib/floriday.functions';
6
+ export * from './lib/floriday-sync.functions';
6
7
  export const floridayAuthenticatieModule = {
7
8
  id: "floriday_connection",
8
9
  name: "Floriday Connection",
9
10
  version: "1.0.0",
10
11
  nav: {
11
12
  label: "Floriday koppeling",
12
- href: "/floriday-authenticatie",
13
+ href: "/floriday-connection",
13
14
  icon: Plug,
14
15
  },
15
16
  settingsCards: [