@fayz-ai/plugin-orders 0.23.0 → 0.24.0-canary.20261028112327
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.
- package/dist/{DeliverySettings-5ACP5XAG.js → DeliverySettings-BDTQKJXC.js} +198 -33
- package/dist/DeliverySettings-BDTQKJXC.js.map +1 -0
- package/dist/chunk-AKOF3KHU.js +174 -0
- package/dist/chunk-AKOF3KHU.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +254 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/delivery-zones.d.ts +67 -0
- package/dist/lib/delivery-zones.d.ts.map +1 -0
- package/dist/lib/onboarding.d.ts +77 -0
- package/dist/lib/onboarding.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/main.browser.js +3 -1
- package/dist/migrations/main.browser.js.map +1 -1
- package/dist/migrations/main.js +140 -2
- package/dist/migrations/main.js.map +1 -1
- package/dist/settings/DeliverySettings.d.ts.map +1 -1
- package/dist/settings/useDeliverySettings.d.ts +3 -0
- package/dist/settings/useDeliverySettings.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/DeliverySettings-5ACP5XAG.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { classifySettingWriteError, normalizeZones, zonesFromSetting, zonesToSetting } from './chunk-AKOF3KHU.js';
|
|
2
|
+
import React2 from 'react';
|
|
2
3
|
import { useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, SettingsGroup, ToggleRow, AddConnectorButton, ConnectorRow, AddConnectorPanel, ConnectorDetail, TextRow } from '@fayz-ai/admin';
|
|
3
|
-
import { Skeleton } from '@fayz-ai/ui';
|
|
4
|
-
import { usePluginRuntimeOptional, getComponent, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
5
|
-
import { AlertTriangle, Loader2, Check } from 'lucide-react';
|
|
4
|
+
import { toast, Skeleton, Button, Input } from '@fayz-ai/ui';
|
|
5
|
+
import { useTranslation, usePluginRuntimeOptional, getComponent, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
6
|
+
import { AlertTriangle, Loader2, Check, Plus, X } from 'lucide-react';
|
|
6
7
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
8
|
|
|
8
|
-
// src/settings/DeliverySettings.tsx
|
|
9
9
|
var DEBOUNCE_MS = 400;
|
|
10
10
|
var KEYS = {
|
|
11
11
|
enabled: "delivery.enabled",
|
|
@@ -13,22 +13,30 @@ var KEYS = {
|
|
|
13
13
|
freeOver: "delivery.free_over",
|
|
14
14
|
minOrder: "delivery.min_order",
|
|
15
15
|
etaMinutes: "delivery.eta_minutes",
|
|
16
|
-
radiusKm: "delivery.radius_km"
|
|
16
|
+
radiusKm: "delivery.radius_km",
|
|
17
|
+
zones: "delivery.zones"
|
|
17
18
|
};
|
|
19
|
+
function fromSetting(field, value) {
|
|
20
|
+
return field === "zones" ? zonesFromSetting(value) : value;
|
|
21
|
+
}
|
|
22
|
+
function toSetting(field, value) {
|
|
23
|
+
if (field === "zones") return zonesToSetting(value ?? []);
|
|
24
|
+
return value ?? null;
|
|
25
|
+
}
|
|
18
26
|
function message(e) {
|
|
19
27
|
if (e instanceof Error) return e.message;
|
|
20
28
|
if (typeof e === "object" && e && "message" in e) return String(e.message);
|
|
21
29
|
return String(e);
|
|
22
30
|
}
|
|
23
31
|
function useDeliverySettings(defaults = {}) {
|
|
24
|
-
const client =
|
|
32
|
+
const client = React2.useMemo(() => getSupabaseClientOptional(), []);
|
|
25
33
|
const supported = !!client;
|
|
26
|
-
const [stored, setStored] =
|
|
27
|
-
const [loading, setLoading] =
|
|
28
|
-
const [saving, setSaving] =
|
|
29
|
-
const [error, setError] =
|
|
30
|
-
const [loadError, setLoadError] =
|
|
31
|
-
|
|
34
|
+
const [stored, setStored] = React2.useState({});
|
|
35
|
+
const [loading, setLoading] = React2.useState(supported);
|
|
36
|
+
const [saving, setSaving] = React2.useState(false);
|
|
37
|
+
const [error, setError] = React2.useState(null);
|
|
38
|
+
const [loadError, setLoadError] = React2.useState(null);
|
|
39
|
+
React2.useEffect(() => {
|
|
32
40
|
if (!client) return;
|
|
33
41
|
let cancelled = false;
|
|
34
42
|
setLoading(true);
|
|
@@ -42,7 +50,7 @@ function useDeliverySettings(defaults = {}) {
|
|
|
42
50
|
const flat = data ?? {};
|
|
43
51
|
const next = {};
|
|
44
52
|
for (const [field, key] of Object.entries(KEYS)) {
|
|
45
|
-
if (key in flat && flat[key] !== null) next[field] = flat[key];
|
|
53
|
+
if (key in flat && flat[key] !== null) next[field] = fromSetting(field, flat[key]);
|
|
46
54
|
else if (key in flat && flat[key] === null) next[field] = null;
|
|
47
55
|
}
|
|
48
56
|
setStored(next);
|
|
@@ -55,12 +63,12 @@ function useDeliverySettings(defaults = {}) {
|
|
|
55
63
|
cancelled = true;
|
|
56
64
|
};
|
|
57
65
|
}, [client]);
|
|
58
|
-
const pendingRef =
|
|
59
|
-
const timerRef =
|
|
60
|
-
|
|
66
|
+
const pendingRef = React2.useRef({});
|
|
67
|
+
const timerRef = React2.useRef(null);
|
|
68
|
+
React2.useEffect(() => () => {
|
|
61
69
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
62
70
|
}, []);
|
|
63
|
-
const set =
|
|
71
|
+
const set = React2.useCallback((patch) => {
|
|
64
72
|
setStored((current) => ({ ...current, ...patch }));
|
|
65
73
|
if (!client) return;
|
|
66
74
|
setError(null);
|
|
@@ -72,14 +80,17 @@ function useDeliverySettings(defaults = {}) {
|
|
|
72
80
|
pendingRef.current = {};
|
|
73
81
|
void Promise.all(
|
|
74
82
|
Object.entries(payload).map(
|
|
75
|
-
([field, value2]) => client.rpc("tenant_setting_set", {
|
|
83
|
+
([field, value2]) => client.rpc("tenant_setting_set", {
|
|
84
|
+
p_key: KEYS[field],
|
|
85
|
+
p_value: toSetting(field, value2)
|
|
86
|
+
}).then(({ error: e }) => {
|
|
76
87
|
if (e) throw e;
|
|
77
88
|
})
|
|
78
89
|
)
|
|
79
|
-
).catch((e) => setError(
|
|
90
|
+
).catch((e) => setError(classifySettingWriteError(e).message)).finally(() => setSaving(false));
|
|
80
91
|
}, DEBOUNCE_MS);
|
|
81
92
|
}, [client]);
|
|
82
|
-
const value =
|
|
93
|
+
const value = React2.useMemo(() => ({ ...defaults, ...stored }), [defaults, stored]);
|
|
83
94
|
return { value, set, loading, saving, error, loadError, supported };
|
|
84
95
|
}
|
|
85
96
|
var MARKETPLACE_CATEGORIES = ["marketplace"];
|
|
@@ -95,9 +106,9 @@ function amountText(value) {
|
|
|
95
106
|
return String(value).replace(".", ",");
|
|
96
107
|
}
|
|
97
108
|
function AmountRow({ label, description, hint, value, onCommit, pending }) {
|
|
98
|
-
const [text, setText] =
|
|
99
|
-
const [invalid, setInvalid] =
|
|
100
|
-
|
|
109
|
+
const [text, setText] = React2.useState(() => amountText(value));
|
|
110
|
+
const [invalid, setInvalid] = React2.useState(false);
|
|
111
|
+
React2.useEffect(() => {
|
|
101
112
|
setText(amountText(value));
|
|
102
113
|
}, [value]);
|
|
103
114
|
return /* @__PURE__ */ jsx(
|
|
@@ -126,25 +137,168 @@ function AmountRow({ label, description, hint, value, onCommit, pending }) {
|
|
|
126
137
|
}
|
|
127
138
|
);
|
|
128
139
|
}
|
|
140
|
+
function ZoneRow({ zone, previousKm, onCommit, onRemove }) {
|
|
141
|
+
const t = useTranslation();
|
|
142
|
+
const [km, setKm] = React2.useState(() => amountText(zone?.maxKm));
|
|
143
|
+
const [fee, setFee] = React2.useState(() => amountText(zone?.fee));
|
|
144
|
+
const [eta, setEta] = React2.useState(() => amountText(zone?.etaMinutes));
|
|
145
|
+
const [invalid, setInvalid] = React2.useState(false);
|
|
146
|
+
React2.useEffect(() => {
|
|
147
|
+
setKm(amountText(zone?.maxKm));
|
|
148
|
+
setFee(amountText(zone?.fee));
|
|
149
|
+
setEta(amountText(zone?.etaMinutes));
|
|
150
|
+
}, [zone?.maxKm, zone?.fee, zone?.etaMinutes]);
|
|
151
|
+
const commit = () => {
|
|
152
|
+
const k = parseAmount(km);
|
|
153
|
+
const f = parseAmount(fee);
|
|
154
|
+
const e = parseAmount(eta);
|
|
155
|
+
if (k === null || f === null || Number.isNaN(k) || Number.isNaN(f) || Number.isNaN(e) || k <= 0) {
|
|
156
|
+
setInvalid(km !== "" || fee !== "");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
setInvalid(false);
|
|
160
|
+
const next = { maxKm: k, fee: f };
|
|
161
|
+
if (e) next.etaMinutes = Math.round(e);
|
|
162
|
+
if (zone && zone.maxKm === next.maxKm && zone.fee === next.fee && zone.etaMinutes === next.etaMinutes) return;
|
|
163
|
+
onCommit(next);
|
|
164
|
+
};
|
|
165
|
+
const onKey = (ev) => {
|
|
166
|
+
if (ev.key === "Enter") ev.target.blur();
|
|
167
|
+
};
|
|
168
|
+
const label = zone ? `${amountText(zone.maxKm)} ${t("orders.zones.km")}` : "";
|
|
169
|
+
return /* @__PURE__ */ jsxs("div", { className: "py-3", children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-sm", children: [
|
|
171
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 whitespace-nowrap", children: [
|
|
172
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: previousKm !== void 0 ? `${amountText(previousKm)} a` : t("orders.zones.upTo") }),
|
|
173
|
+
/* @__PURE__ */ jsx(
|
|
174
|
+
Input,
|
|
175
|
+
{
|
|
176
|
+
className: "h-9 w-16",
|
|
177
|
+
inputMode: "decimal",
|
|
178
|
+
value: km,
|
|
179
|
+
placeholder: "3",
|
|
180
|
+
onChange: (e) => {
|
|
181
|
+
setKm(e.target.value);
|
|
182
|
+
setInvalid(false);
|
|
183
|
+
},
|
|
184
|
+
onBlur: commit,
|
|
185
|
+
onKeyDown: onKey,
|
|
186
|
+
"aria-label": `${t("orders.zones.upTo")} ${t("orders.zones.km")}`
|
|
187
|
+
}
|
|
188
|
+
),
|
|
189
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("orders.zones.km") })
|
|
190
|
+
] }),
|
|
191
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 whitespace-nowrap", children: [
|
|
192
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "R$" }),
|
|
193
|
+
/* @__PURE__ */ jsx(
|
|
194
|
+
Input,
|
|
195
|
+
{
|
|
196
|
+
className: "h-9 w-20",
|
|
197
|
+
inputMode: "decimal",
|
|
198
|
+
value: fee,
|
|
199
|
+
placeholder: "0,00",
|
|
200
|
+
onChange: (e) => {
|
|
201
|
+
setFee(e.target.value);
|
|
202
|
+
setInvalid(false);
|
|
203
|
+
},
|
|
204
|
+
onBlur: commit,
|
|
205
|
+
onKeyDown: onKey,
|
|
206
|
+
"aria-label": t("orders.zones.fee")
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
] }),
|
|
210
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 whitespace-nowrap", children: [
|
|
211
|
+
/* @__PURE__ */ jsx(
|
|
212
|
+
Input,
|
|
213
|
+
{
|
|
214
|
+
className: "h-9 w-16",
|
|
215
|
+
inputMode: "numeric",
|
|
216
|
+
value: eta,
|
|
217
|
+
placeholder: "30",
|
|
218
|
+
onChange: (e) => {
|
|
219
|
+
setEta(e.target.value);
|
|
220
|
+
setInvalid(false);
|
|
221
|
+
},
|
|
222
|
+
onBlur: commit,
|
|
223
|
+
onKeyDown: onKey,
|
|
224
|
+
"aria-label": t("orders.zones.eta")
|
|
225
|
+
}
|
|
226
|
+
),
|
|
227
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("orders.zones.eta") })
|
|
228
|
+
] }),
|
|
229
|
+
/* @__PURE__ */ jsx(
|
|
230
|
+
Button,
|
|
231
|
+
{
|
|
232
|
+
type: "button",
|
|
233
|
+
variant: "ghost",
|
|
234
|
+
size: "icon",
|
|
235
|
+
className: "ml-auto h-9 w-9",
|
|
236
|
+
onClick: onRemove,
|
|
237
|
+
"aria-label": t("orders.zones.remove", { km: label }),
|
|
238
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
] }),
|
|
242
|
+
invalid && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: t("orders.zones.invalid") })
|
|
243
|
+
] });
|
|
244
|
+
}
|
|
245
|
+
function ZonesGroup({ zones, onChange }) {
|
|
246
|
+
const t = useTranslation();
|
|
247
|
+
const [draft, setDraft] = React2.useState(false);
|
|
248
|
+
const sorted = React2.useMemo(() => normalizeZones(zones), [zones]);
|
|
249
|
+
return /* @__PURE__ */ jsxs(SettingsGroup, { title: t("orders.zones.title"), description: t("orders.zones.description"), children: [
|
|
250
|
+
sorted.length === 0 && !draft && /* @__PURE__ */ jsx("p", { className: "py-3 text-sm text-muted-foreground", children: t("orders.zones.empty") }),
|
|
251
|
+
sorted.map((zone, i) => /* @__PURE__ */ jsx(
|
|
252
|
+
ZoneRow,
|
|
253
|
+
{
|
|
254
|
+
zone,
|
|
255
|
+
previousKm: i > 0 ? sorted[i - 1].maxKm : void 0,
|
|
256
|
+
onCommit: (next) => onChange(normalizeZones(sorted.map((z, j) => j === i ? next : z))),
|
|
257
|
+
onRemove: () => onChange(sorted.filter((_, j) => j !== i))
|
|
258
|
+
},
|
|
259
|
+
`${zone.maxKm}-${i}`
|
|
260
|
+
)),
|
|
261
|
+
draft && /* @__PURE__ */ jsx(
|
|
262
|
+
ZoneRow,
|
|
263
|
+
{
|
|
264
|
+
previousKm: sorted.length > 0 ? sorted[sorted.length - 1].maxKm : void 0,
|
|
265
|
+
onCommit: (next) => {
|
|
266
|
+
setDraft(false);
|
|
267
|
+
onChange(normalizeZones([...sorted, next]));
|
|
268
|
+
},
|
|
269
|
+
onRemove: () => setDraft(false)
|
|
270
|
+
}
|
|
271
|
+
),
|
|
272
|
+
/* @__PURE__ */ jsx("div", { className: "py-3", children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setDraft(true), disabled: draft, children: [
|
|
273
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1 h-3.5 w-3.5" }),
|
|
274
|
+
t("orders.zones.add")
|
|
275
|
+
] }) })
|
|
276
|
+
] });
|
|
277
|
+
}
|
|
129
278
|
function DeliverySettings() {
|
|
279
|
+
const t = useTranslation();
|
|
130
280
|
const runtime = usePluginRuntimeOptional();
|
|
131
281
|
const shopActive = (runtime?.activePlugins ?? []).some((p) => p.id === "shop");
|
|
132
282
|
const ShopDelivery = shopActive ? getComponent(SHOP_DELIVERY_COMPONENT) : void 0;
|
|
133
283
|
const settings = useDeliverySettings({ enabled: true, fee: 0, minOrder: 0, etaMinutes: 45, radiusKm: 0 });
|
|
134
284
|
const { value, set } = settings;
|
|
135
285
|
const { connectors } = useConnectors({ hostPluginId: "orders", includeGrants: false });
|
|
136
|
-
const [adding, setAdding] =
|
|
137
|
-
const [selected, setSelected] =
|
|
286
|
+
const [adding, setAdding] = React2.useState(false);
|
|
287
|
+
const [selected, setSelected] = React2.useState(null);
|
|
138
288
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
139
|
-
const openJourney =
|
|
289
|
+
const openJourney = React2.useCallback((connector) => {
|
|
140
290
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
141
291
|
else setSelected(connector);
|
|
142
292
|
}, [panelAvailable]);
|
|
143
|
-
const marketplaces =
|
|
293
|
+
const marketplaces = React2.useMemo(
|
|
144
294
|
() => (connectors ?? []).filter((c) => c.category && MARKETPLACE_CATEGORIES.includes(c.category)),
|
|
145
295
|
[connectors]
|
|
146
296
|
);
|
|
147
297
|
const patch = (next) => set(next);
|
|
298
|
+
const zones = value.zones ?? [];
|
|
299
|
+
React2.useEffect(() => {
|
|
300
|
+
if (settings.error) toast.error(t("orders.delivery.saveFailed"), { description: settings.error });
|
|
301
|
+
}, [settings.error, t]);
|
|
148
302
|
if (!settings.supported) {
|
|
149
303
|
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-amber-500/30 bg-amber-500/5 p-4 text-sm", children: [
|
|
150
304
|
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 font-medium text-amber-700 dark:text-amber-500", children: [
|
|
@@ -238,7 +392,10 @@ function DeliverySettings() {
|
|
|
238
392
|
onCommit: (next) => patch({ etaMinutes: next === null ? 0 : Math.round(next) })
|
|
239
393
|
}
|
|
240
394
|
),
|
|
241
|
-
|
|
395
|
+
zones.length > 0 ? (
|
|
396
|
+
// With bands the radius is derived: the largest band is how far the moto goes.
|
|
397
|
+
/* @__PURE__ */ jsx("p", { className: "py-3 text-sm text-muted-foreground", children: t("orders.zones.radius", { km: amountText(zones[zones.length - 1].maxKm) }) })
|
|
398
|
+
) : /* @__PURE__ */ jsx(
|
|
242
399
|
AmountRow,
|
|
243
400
|
{
|
|
244
401
|
pending: true,
|
|
@@ -251,6 +408,13 @@ function DeliverySettings() {
|
|
|
251
408
|
)
|
|
252
409
|
]
|
|
253
410
|
}
|
|
411
|
+
),
|
|
412
|
+
/* @__PURE__ */ jsx(
|
|
413
|
+
ZonesGroup,
|
|
414
|
+
{
|
|
415
|
+
zones,
|
|
416
|
+
onChange: (next) => patch(next.length > 0 ? { zones: next, radiusKm: next[next.length - 1].maxKm } : { zones: next })
|
|
417
|
+
}
|
|
254
418
|
)
|
|
255
419
|
] }),
|
|
256
420
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -263,9 +427,10 @@ function DeliverySettings() {
|
|
|
263
427
|
] }),
|
|
264
428
|
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card shadow-sm", children: marketplaces.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Nenhum marketplace dispon\xEDvel para este app ainda. A entrega pr\xF3pria funciona sem nenhum." }) : /* @__PURE__ */ jsx("ul", { className: "divide-y", children: marketplaces.map((connector) => /* @__PURE__ */ jsx(ConnectorRow, { connector, onOpen: openJourney }, connector.id)) }) })
|
|
265
429
|
] }),
|
|
266
|
-
/* @__PURE__ */ jsx("div", { className: "flex min-h-5 items-center gap-1.5 px-1 text-xs text-muted-foreground", children: settings.error ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-destructive", children: [
|
|
430
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-5 items-center gap-1.5 px-1 text-xs text-muted-foreground", children: settings.error ? /* @__PURE__ */ jsxs("span", { role: "alert", className: "flex items-center gap-1.5 text-destructive", children: [
|
|
267
431
|
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
268
|
-
"
|
|
432
|
+
t("orders.delivery.saveFailed"),
|
|
433
|
+
": ",
|
|
269
434
|
settings.error
|
|
270
435
|
] }) : settings.saving ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
271
436
|
/* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 shrink-0 animate-spin" }),
|
|
@@ -301,5 +466,5 @@ function DeliverySettings() {
|
|
|
301
466
|
}
|
|
302
467
|
|
|
303
468
|
export { DeliverySettings };
|
|
304
|
-
//# sourceMappingURL=DeliverySettings-
|
|
305
|
-
//# sourceMappingURL=DeliverySettings-
|
|
469
|
+
//# sourceMappingURL=DeliverySettings-BDTQKJXC.js.map
|
|
470
|
+
//# sourceMappingURL=DeliverySettings-BDTQKJXC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/settings/useDeliverySettings.ts","../src/settings/DeliverySettings.tsx"],"names":["React","value"],"mappings":"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","file":"DeliverySettings-BDTQKJXC.js","sourcesContent":["import React from 'react'\nimport { getSupabaseClientOptional } from '@fayz-ai/core'\nimport { classifySettingWriteError, zonesFromSetting, zonesToSetting, type DeliveryZone } from '../lib/delivery-zones'\n\n// ---------------------------------------------------------------------------\n// useDeliverySettings — a política de entrega da casa, lida e salva\n// ---------------------------------------------------------------------------\n// Mesma forma do `useStoreSettings` do plugin-shop, e pela mesma razão: cada\n// linha é UMA decisão com significado imediato (\"entrego em 45 min\"), então\n// salva sozinha, com debounce, e a tela diz \"salvando…\" em vez de pedir que\n// alguém lembre no que tocou.\n//\n// Onde mora: `tenants.settings`, pelas RPCs do spine (`tenant_settings` para\n// ler tudo de uma vez, `tenant_setting_set` para gravar chave a chave). A\n// função recusa chave não registrada — ver a migration 041 — e cobra\n// `settings.manage`, então o garçom não muda a taxa.\n\nconst DEBOUNCE_MS = 400\nconst PREFIX = 'delivery.'\n\nexport interface DeliverySettings {\n enabled?: boolean\n fee?: number\n freeOver?: number | null\n minOrder?: number\n etaMinutes?: number\n radiusKm?: number\n /** Bands by distance (migration 008). Empty = the single `fee` rules. */\n zones?: DeliveryZone[]\n}\n\n/** Chave no banco ⇄ campo na tela. Uma tabela, para os dois sentidos. */\nconst KEYS: Record<keyof DeliverySettings, string> = {\n enabled: 'delivery.enabled',\n fee: 'delivery.fee',\n freeOver: 'delivery.free_over',\n minOrder: 'delivery.min_order',\n etaMinutes: 'delivery.eta_minutes',\n radiusKm: 'delivery.radius_km',\n zones: 'delivery.zones',\n}\n\n// The pool stores snake_case rows; the screen speaks DeliveryZone.\nfunction fromSetting(field: keyof DeliverySettings, value: unknown): unknown {\n return field === 'zones' ? zonesFromSetting(value) : value\n}\nfunction toSetting(field: keyof DeliverySettings, value: unknown): unknown {\n if (field === 'zones') return zonesToSetting((value as DeliveryZone[] | undefined) ?? [])\n return value ?? null\n}\n\nexport interface DeliverySettingsHandle {\n value: DeliverySettings\n set: (patch: DeliverySettings) => void\n loading: boolean\n saving: boolean\n /** A gravação foi recusada. O valor otimista continua na tela. */\n error: string | null\n /** A LEITURA falhou: o que está na tela são padrões, não as escolhas da casa. */\n loadError: string | null\n /** Sem backend montado (mock/standalone): a tela diz isso em vez de fingir. */\n supported: boolean\n}\n\nfunction message(e: unknown): string {\n if (e instanceof Error) return e.message\n if (typeof e === 'object' && e && 'message' in e) return String((e as { message: unknown }).message)\n return String(e)\n}\n\nexport function useDeliverySettings(defaults: DeliverySettings = {}): DeliverySettingsHandle {\n const client = React.useMemo(() => getSupabaseClientOptional() as any, [])\n const supported = !!client\n\n const [stored, setStored] = React.useState<DeliverySettings>({})\n const [loading, setLoading] = React.useState(supported)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!client) return\n let cancelled = false\n setLoading(true)\n setLoadError(null)\n void client\n .rpc('tenant_settings')\n .then(({ data, error: e }: { data: Record<string, unknown> | null; error: unknown }) => {\n if (cancelled) return\n if (e) { setLoadError(message(e)); return }\n const flat = (data ?? {}) as Record<string, unknown>\n const next: DeliverySettings = {}\n for (const [field, key] of Object.entries(KEYS)) {\n // Só o que a casa DECIDIU. Uma chave ausente continua indefinida, e é\n // o `defaults` da tela que responde por ela — gravar o padrão de volta\n // transformaria \"não decidi\" em \"escolhi isto\".\n if (key in flat && flat[key] !== null) (next as any)[field] = fromSetting(field as keyof DeliverySettings, flat[key])\n else if (key in flat && flat[key] === null) (next as any)[field] = null\n }\n setStored(next)\n })\n .catch((e: unknown) => { if (!cancelled) setLoadError(message(e)) })\n .finally(() => { if (!cancelled) setLoading(false) })\n return () => { cancelled = true }\n }, [client])\n\n // O patch acumula durante a janela do debounce: três cliques viram uma\n // rodada de gravações, não três corridas para ver quem grava por último.\n const pendingRef = React.useRef<DeliverySettings>({})\n const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n React.useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current) }, [])\n\n const set = React.useCallback((patch: DeliverySettings) => {\n setStored((current) => ({ ...current, ...patch }))\n if (!client) return\n setError(null)\n setSaving(true)\n pendingRef.current = { ...pendingRef.current, ...patch }\n if (timerRef.current) clearTimeout(timerRef.current)\n timerRef.current = setTimeout(() => {\n const payload = pendingRef.current\n pendingRef.current = {}\n void Promise.all(\n Object.entries(payload).map(([field, value]) =>\n client.rpc('tenant_setting_set', {\n p_key: KEYS[field as keyof DeliverySettings],\n p_value: toSetting(field as keyof DeliverySettings, value),\n })\n .then(({ error: e }: { error: unknown }) => { if (e) throw e }),\n ),\n )\n // \"unknown key delivery.zones\" names the missing migration instead of\n // reading as a generic refusal.\n .catch((e: unknown) => setError(classifySettingWriteError(e).message))\n .finally(() => setSaving(false))\n }, DEBOUNCE_MS)\n }, [client])\n\n const value = React.useMemo(() => ({ ...defaults, ...stored }), [defaults, stored])\n\n return { value, set, loading, saving, error, loadError, supported }\n}\n","import React from 'react'\nimport {\n SettingsGroup, ToggleRow, TextRow,\n useConnectors, ConnectorRow, ConnectorDetail, AddConnectorPanel, AddConnectorButton,\n openConnectorsPanel, useConnectorsPanelAvailable,\n type UnifiedConnector,\n} from '@fayz-ai/admin'\nimport { Button, Input, Skeleton, toast } from '@fayz-ai/ui'\nimport { getComponent, usePluginRuntimeOptional, useTranslation } from '@fayz-ai/core'\nimport { AlertTriangle, Check, Loader2, Plus, X } from 'lucide-react'\nimport { useDeliverySettings, type DeliverySettings as DeliveryValues } from './useDeliverySettings'\nimport { normalizeZones, type DeliveryZone } from '../lib/delivery-zones'\n\n/**\n * Entrega — o que a casa cobra para levar, e quem traz o pedido.\n *\n * Duas coisas na tela, e a distinção é o assunto (a mesma do \"Pagamentos\" da\n * loja, e por isso a mesma forma):\n *\n * • A POLÍTICA é da casa e muda com a estação: taxa, mínimo, tempo, raio.\n * Mora em `tenants.settings` (migration 041) e salva sozinha.\n *\n * • QUEM TRAZ O PEDIDO é uma conexão. iFood, 99Food, Keeta, Uber Eats já são\n * conectores declarados por este plugin; a tela FILTRA o hub por\n * `category === 'marketplace'` e reusa a mesma linha, a mesma marca e a\n * mesma jornada de conectar. Um cartão próprio aqui divergiria do hub na\n * primeira vez que qualquer um dos três mudasse.\n *\n * Entrega própria é legítima sem conector nenhum — a moto é do restaurante —\n * então nada aqui exige uma conexão para a política valer.\n */\nconst MARKETPLACE_CATEGORIES = ['marketplace']\n// A store with a SITE sets its delivery through the shop's own block\n// (`shop.delivery-settings`, shop 0130): the fee, free delivery and regions the\n// quote charges from, the minimum and the estimate the site enforces and shows.\n// Without a shop the policy rows below still save to tenants.settings (spine\n// 041) and nothing reads them yet — hence their «Em breve» mark.\nconst SHOP_DELIVERY_COMPONENT = 'shop.delivery-settings'\n\n/** Aceita \"12,90\" e \"12.90\"; devolve `null` para vazio. */\nfunction parseAmount(text: string): number | null {\n const clean = text.replace(/\\s/g, '').replace(',', '.')\n if (!clean) return null\n const n = Number(clean)\n return Number.isFinite(n) && n >= 0 ? n : NaN\n}\n\nfunction amountText(value: number | null | undefined): string {\n if (value === null || value === undefined) return ''\n return String(value).replace('.', ',')\n}\n\n/** Uma linha de dinheiro/número: texto local no input, número no banco. */\nfunction AmountRow({ label, description, hint, value, onCommit, pending }: {\n pending?: boolean\n label: string\n description?: string\n hint?: string\n value: number | null | undefined\n onCommit: (next: number | null) => void\n}) {\n const [text, setText] = React.useState(() => amountText(value))\n const [invalid, setInvalid] = React.useState(false)\n // O valor do servidor ganha quando muda por fora (carregou, ou outra aba).\n React.useEffect(() => { setText(amountText(value)) }, [value])\n\n return (\n <TextRow\n pending={pending}\n label={label}\n description={description}\n value={text}\n onChange={(next) => { setText(next); setInvalid(false) }}\n // Só no blur/Enter: salvar por tecla gravaria '1' e '19' no caminho para\n // '19,90', e cada um deles é uma taxa que a casa cobrou de verdade.\n onBlurCommit={() => {\n const parsed = parseAmount(text)\n if (Number.isNaN(parsed)) { setInvalid(true); return }\n setInvalid(false)\n onCommit(parsed)\n }}\n hint={invalid ? 'Use um número, como 9,90.' : hint}\n invalid={invalid}\n placeholder=\"0,00\"\n />\n )\n}\n\n/** One band, edited in place: \"até [3] km · R$ [5,00] · [30] min\". Saves on blur. */\nfunction ZoneRow({ zone, previousKm, onCommit, onRemove }: {\n /** Absent = a new band being typed; nothing is saved until it parses. */\n zone?: DeliveryZone\n previousKm?: number\n onCommit: (next: DeliveryZone) => void\n onRemove: () => void\n}) {\n const t = useTranslation()\n const [km, setKm] = React.useState(() => amountText(zone?.maxKm))\n const [fee, setFee] = React.useState(() => amountText(zone?.fee))\n const [eta, setEta] = React.useState(() => amountText(zone?.etaMinutes))\n const [invalid, setInvalid] = React.useState(false)\n React.useEffect(() => {\n setKm(amountText(zone?.maxKm)); setFee(amountText(zone?.fee)); setEta(amountText(zone?.etaMinutes))\n }, [zone?.maxKm, zone?.fee, zone?.etaMinutes])\n\n const commit = () => {\n const k = parseAmount(km)\n const f = parseAmount(fee)\n const e = parseAmount(eta)\n if (k === null || f === null || Number.isNaN(k) || Number.isNaN(f) || Number.isNaN(e) || k <= 0) {\n // A new row stays blank until it parses; an existing one keeps its value.\n setInvalid(km !== '' || fee !== '')\n return\n }\n setInvalid(false)\n const next: DeliveryZone = { maxKm: k, fee: f }\n if (e) next.etaMinutes = Math.round(e)\n if (zone && zone.maxKm === next.maxKm && zone.fee === next.fee && zone.etaMinutes === next.etaMinutes) return\n onCommit(next)\n }\n const onKey = (ev: React.KeyboardEvent) => { if (ev.key === 'Enter') (ev.target as HTMLElement).blur() }\n const label = zone ? `${amountText(zone.maxKm)} ${t('orders.zones.km')}` : ''\n\n return (\n <div className=\"py-3\">\n {/* Each value keeps its unit beside it: a narrow column wraps between the\n groups (\"até 3 km\" / \"R$ 5,00\" / \"30 min\"), never inside one. */}\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-2 text-sm\">\n <span className=\"inline-flex items-center gap-1.5 whitespace-nowrap\">\n <span className=\"text-muted-foreground\">\n {previousKm !== undefined ? `${amountText(previousKm)} a` : t('orders.zones.upTo')}\n </span>\n <Input className=\"h-9 w-16\" inputMode=\"decimal\" value={km} placeholder=\"3\"\n onChange={(e) => { setKm(e.target.value); setInvalid(false) }} onBlur={commit} onKeyDown={onKey}\n aria-label={`${t('orders.zones.upTo')} ${t('orders.zones.km')}`} />\n <span className=\"text-muted-foreground\">{t('orders.zones.km')}</span>\n </span>\n <span className=\"inline-flex items-center gap-1.5 whitespace-nowrap\">\n <span className=\"text-muted-foreground\">R$</span>\n <Input className=\"h-9 w-20\" inputMode=\"decimal\" value={fee} placeholder=\"0,00\"\n onChange={(e) => { setFee(e.target.value); setInvalid(false) }} onBlur={commit} onKeyDown={onKey}\n aria-label={t('orders.zones.fee')} />\n </span>\n <span className=\"inline-flex items-center gap-1.5 whitespace-nowrap\">\n <Input className=\"h-9 w-16\" inputMode=\"numeric\" value={eta} placeholder=\"30\"\n onChange={(e) => { setEta(e.target.value); setInvalid(false) }} onBlur={commit} onKeyDown={onKey}\n aria-label={t('orders.zones.eta')} />\n <span className=\"text-muted-foreground\">{t('orders.zones.eta')}</span>\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" className=\"ml-auto h-9 w-9\" onClick={onRemove}\n aria-label={t('orders.zones.remove', { km: label })}>\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n {invalid && <p className=\"mt-1 text-xs text-destructive\">{t('orders.zones.invalid')}</p>}\n </div>\n )\n}\n\n/** The bands, as a list that edits in place (AGENTS.md rule 9: no modal). */\nfunction ZonesGroup({ zones, onChange }: { zones: DeliveryZone[]; onChange: (next: DeliveryZone[]) => void }) {\n const t = useTranslation()\n const [draft, setDraft] = React.useState(false)\n const sorted = React.useMemo(() => normalizeZones(zones), [zones])\n return (\n <SettingsGroup title={t('orders.zones.title')} description={t('orders.zones.description')}>\n {sorted.length === 0 && !draft && (\n <p className=\"py-3 text-sm text-muted-foreground\">{t('orders.zones.empty')}</p>\n )}\n {sorted.map((zone, i) => (\n <ZoneRow\n key={`${zone.maxKm}-${i}`}\n zone={zone}\n previousKm={i > 0 ? sorted[i - 1].maxKm : undefined}\n onCommit={(next) => onChange(normalizeZones(sorted.map((z, j) => (j === i ? next : z))))}\n onRemove={() => onChange(sorted.filter((_, j) => j !== i))}\n />\n ))}\n {draft && (\n <ZoneRow\n previousKm={sorted.length > 0 ? sorted[sorted.length - 1].maxKm : undefined}\n onCommit={(next) => { setDraft(false); onChange(normalizeZones([...sorted, next])) }}\n onRemove={() => setDraft(false)}\n />\n )}\n <div className=\"py-3\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setDraft(true)} disabled={draft}>\n <Plus className=\"mr-1 h-3.5 w-3.5\" />\n {t('orders.zones.add')}\n </Button>\n </div>\n </SettingsGroup>\n )\n}\n\nexport function DeliverySettings() {\n const t = useTranslation()\n const runtime = usePluginRuntimeOptional()\n const shopActive = (runtime?.activePlugins ?? []).some((p) => (p as { id?: string }).id === 'shop')\n const ShopDelivery = shopActive\n ? (getComponent(SHOP_DELIVERY_COMPONENT) as React.ComponentType<unknown> | undefined)\n : undefined\n const settings = useDeliverySettings({ enabled: true, fee: 0, minOrder: 0, etaMinutes: 45, radiusKm: 0 })\n const { value, set } = settings\n const { connectors } = useConnectors({ hostPluginId: 'orders', includeGrants: false })\n const [adding, setAdding] = React.useState(false)\n const [selected, setSelected] = React.useState<UnifiedConnector | null>(null)\n const panelAvailable = useConnectorsPanelAvailable()\n\n const openJourney = React.useCallback((connector: UnifiedConnector) => {\n // Mesma regra do hub: a gaveta do assistente é dona da jornada quando está\n // montada, e a folha é o fallback para o fluxo nunca ficar sem saída.\n if (panelAvailable) openConnectorsPanel(connector)\n else setSelected(connector)\n }, [panelAvailable])\n\n const marketplaces = React.useMemo(\n () => (connectors ?? []).filter((c) => c.category && MARKETPLACE_CATEGORIES.includes(c.category)),\n [connectors],\n )\n\n const patch = (next: DeliveryValues) => set(next)\n const zones = value.zones ?? []\n\n // A refused write must not pass as saved: the footer line is easy to miss\n // under the connectors, so the refusal is also a toast with the cause.\n React.useEffect(() => {\n if (settings.error) toast.error(t('orders.delivery.saveFailed'), { description: settings.error })\n }, [settings.error, t])\n\n if (!settings.supported) {\n return (\n <div className=\"rounded-lg border border-amber-500/30 bg-amber-500/5 p-4 text-sm\">\n <p className=\"flex items-center gap-2 font-medium text-amber-700 dark:text-amber-500\">\n <AlertTriangle className=\"h-4 w-4 shrink-0\" />\n Este app não guarda a configuração de entrega\n </p>\n <p className=\"mt-1 text-muted-foreground\">\n Não há backend montado aqui, então nada abaixo seria salvo — e por isso não é mostrado.\n </p>\n </div>\n )\n }\n\n if (settings.loading) {\n return (\n <div className=\"space-y-4\">\n <Skeleton className=\"h-28 w-full rounded-lg\" />\n <Skeleton className=\"h-40 w-full rounded-lg\" />\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {settings.loadError && (\n <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm\">\n <p className=\"flex items-center gap-2 font-medium text-destructive\">\n <AlertTriangle className=\"h-4 w-4 shrink-0\" />\n Não foi possível carregar a configuração de entrega\n </p>\n <p className=\"mt-1 text-muted-foreground\">\n O que aparece abaixo são os valores padrão, não as suas escolhas. Recarregue a página\n antes de alterar algo — salvar agora sobrescreveria o que não conseguimos ler.\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">{settings.loadError}</p>\n </div>\n )}\n\n {ShopDelivery ? <ShopDelivery /> : <>\n <SettingsGroup\n title=\"Entrega\"\n description=\"Vale para o pedido que sai da casa. O quadro de despacho continua existindo com a entrega desligada — o histórico não some.\"\n >\n <ToggleRow\n pending\n label=\"A casa entrega\"\n description=\"Desligado, nenhum canal novo nasce como entrega.\"\n checked={value.enabled !== false}\n onChange={(checked) => patch({ enabled: checked })}\n />\n <AmountRow\n pending\n label=\"Taxa de entrega\"\n description=\"Cobrada por pedido, na moeda da casa.\"\n hint=\"Zero = entrega sem taxa.\"\n value={value.fee}\n onCommit={(next) => patch({ fee: next ?? 0 })}\n />\n <AmountRow\n pending\n label=\"Frete grátis acima de\"\n description=\"Acima deste valor de pedido a taxa é zerada.\"\n hint=\"Vazio = a taxa vale sempre.\"\n value={value.freeOver}\n onCommit={(next) => patch({ freeOver: next })}\n />\n <AmountRow\n pending\n label=\"Pedido mínimo\"\n description=\"Abaixo disso a casa não entrega.\"\n hint=\"Zero = sem mínimo.\"\n value={value.minOrder}\n onCommit={(next) => patch({ minOrder: next ?? 0 })}\n />\n </SettingsGroup>\n\n <SettingsGroup\n title=\"Até onde, e em quanto tempo\"\n description=\"O que o cliente ouve quando pergunta — e o que o despacho promete.\"\n >\n <AmountRow\n pending\n label=\"Tempo estimado (min)\"\n description=\"Da confirmação até a porta.\"\n value={value.etaMinutes}\n onCommit={(next) => patch({ etaMinutes: next === null ? 0 : Math.round(next) })}\n />\n {zones.length > 0 ? (\n // With bands the radius is derived: the largest band is how far the moto goes.\n <p className=\"py-3 text-sm text-muted-foreground\">\n {t('orders.zones.radius', { km: amountText(zones[zones.length - 1].maxKm) })}\n </p>\n ) : (\n <AmountRow\n pending\n label=\"Raio de entrega (km)\"\n description=\"Distância máxima a partir da casa.\"\n hint=\"Zero = sem limite declarado.\"\n value={value.radiusKm}\n onCommit={(next) => patch({ radiusKm: next ?? 0 })}\n />\n )}\n </SettingsGroup>\n\n <ZonesGroup\n zones={zones}\n onChange={(next) => patch(next.length > 0\n ? { zones: next, radiusKm: next[next.length - 1].maxKm }\n : { zones: next })}\n />\n </>}\n\n <div>\n <div className=\"flex items-start justify-between gap-4 px-1 pb-1 pt-4\">\n <div>\n <h3 className=\"text-sm font-semibold\">Quem traz o pedido</h3>\n <p className=\"mt-0.5 text-[10px] text-muted-foreground\">\n Marketplaces conectados entram no quadro sozinhos, cada pedido marcado com a origem.\n A credencial vai para o cofre, nunca para o banco da casa.\n </p>\n </div>\n <AddConnectorButton onClick={() => setAdding(true)} />\n </div>\n <div className=\"rounded-card border bg-card shadow-sm\">\n {marketplaces.length === 0 ? (\n <p className=\"p-4 text-sm text-muted-foreground\">\n Nenhum marketplace disponível para este app ainda. A entrega própria funciona sem nenhum.\n </p>\n ) : (\n <ul className=\"divide-y\">\n {marketplaces.map((connector) => (\n <ConnectorRow key={connector.id} connector={connector} onOpen={openJourney} />\n ))}\n </ul>\n )}\n </div>\n </div>\n\n <div className=\"flex min-h-5 items-center gap-1.5 px-1 text-xs text-muted-foreground\">\n {settings.error ? (\n <span role=\"alert\" className=\"flex items-center gap-1.5 text-destructive\">\n <AlertTriangle className=\"h-3.5 w-3.5 shrink-0\" />\n {t('orders.delivery.saveFailed')}: {settings.error}\n </span>\n ) : settings.saving ? (\n <>\n <Loader2 className=\"h-3.5 w-3.5 shrink-0 animate-spin\" />\n Salvando…\n </>\n ) : (\n <>\n <Check className=\"h-3.5 w-3.5 shrink-0\" />\n As alterações são salvas automaticamente.\n </>\n )}\n </div>\n\n <AddConnectorPanel\n open={adding}\n onOpenChange={setAdding}\n connectors={connectors}\n categories={MARKETPLACE_CATEGORIES}\n onSelect={(connector) => { setAdding(false); openJourney(connector) }}\n />\n <ConnectorDetail\n connector={selected}\n open={!!selected}\n onOpenChange={(open) => { if (!open) setSelected(null) }}\n />\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/lib/delivery-zones.ts
|
|
2
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
3
|
+
function normalizeZones(input) {
|
|
4
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5
|
+
const out = [];
|
|
6
|
+
for (const raw of input) {
|
|
7
|
+
if (!raw) continue;
|
|
8
|
+
const maxKm = Number(raw.maxKm);
|
|
9
|
+
const fee = Number(raw.fee);
|
|
10
|
+
if (!Number.isFinite(maxKm) || maxKm <= 0) continue;
|
|
11
|
+
if (!Number.isFinite(fee) || fee < 0) continue;
|
|
12
|
+
const eta = raw.etaMinutes === void 0 || raw.etaMinutes === null ? void 0 : Number(raw.etaMinutes);
|
|
13
|
+
const zone = { maxKm: round2(maxKm), fee: round2(fee) };
|
|
14
|
+
if (eta !== void 0 && Number.isFinite(eta) && eta > 0) zone.etaMinutes = Math.round(eta);
|
|
15
|
+
out.push(zone);
|
|
16
|
+
}
|
|
17
|
+
out.sort((a, b) => a.maxKm - b.maxKm);
|
|
18
|
+
return out.filter((z) => seen.has(z.maxKm) ? false : (seen.add(z.maxKm), true));
|
|
19
|
+
}
|
|
20
|
+
function zonesFromSetting(value) {
|
|
21
|
+
if (!Array.isArray(value)) return [];
|
|
22
|
+
return normalizeZones(value.map((row) => {
|
|
23
|
+
if (!row || typeof row !== "object") return null;
|
|
24
|
+
const r = row;
|
|
25
|
+
return {
|
|
26
|
+
maxKm: Number(r.max_km ?? r.maxKm),
|
|
27
|
+
fee: Number(r.fee),
|
|
28
|
+
etaMinutes: r.eta_minutes ?? r.etaMinutes ?? void 0
|
|
29
|
+
};
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
function zonesToSetting(zones) {
|
|
33
|
+
return normalizeZones(zones).map((z) => ({
|
|
34
|
+
max_km: z.maxKm,
|
|
35
|
+
fee: z.fee,
|
|
36
|
+
eta_minutes: z.etaMinutes ?? null
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
var KM = String.raw`(?:km|kms|quil[oô]metros?)`;
|
|
40
|
+
var NUM = String.raw`(\d+(?:\.\d+)?)`;
|
|
41
|
+
var TOKEN = new RegExp([
|
|
42
|
+
String.raw`(?<rangeA>${NUM})\s*(?:a|-|–|até)\s*(?<rangeB>${NUM})\s*${KM}\b`,
|
|
43
|
+
String.raw`entre\s+(?<betweenA>${NUM})\s+e\s+(?<betweenB>${NUM})\s*${KM}\b`,
|
|
44
|
+
String.raw`r\$\s*(?<feeRs>${NUM})`,
|
|
45
|
+
String.raw`(?<unitNum>${NUM})\s*(?<unit>${KM}|reais|real|contos?|pila|minutos?|mins?|min|horas?|h)\b`,
|
|
46
|
+
String.raw`(?<free>gr[aá]tis|de gra[cç]a|sem taxa|free)`,
|
|
47
|
+
String.raw`\bem\s+(?<emNum>${NUM})\s*(?<emUnit>horas?|h|minutos?|mins?|min)?\b(?!\s*(?:${KM}|reais|real|r\$))`,
|
|
48
|
+
String.raw`(?<bare>${NUM})`
|
|
49
|
+
].join("|"), "gi");
|
|
50
|
+
function tokenize(text) {
|
|
51
|
+
const s = text.toLowerCase().replace(/(\d),(\d)/g, "$1.$2");
|
|
52
|
+
const tokens = [];
|
|
53
|
+
for (const m of s.matchAll(TOKEN)) {
|
|
54
|
+
const g = m.groups ?? {};
|
|
55
|
+
if (g.rangeB !== void 0) tokens.push({ kind: "km", value: Number(g.rangeB) });
|
|
56
|
+
else if (g.betweenB !== void 0) tokens.push({ kind: "km", value: Number(g.betweenB) });
|
|
57
|
+
else if (g.feeRs !== void 0) tokens.push({ kind: "fee", value: Number(g.feeRs) });
|
|
58
|
+
else if (g.unitNum !== void 0) {
|
|
59
|
+
const value = Number(g.unitNum);
|
|
60
|
+
const unit = g.unit ?? "";
|
|
61
|
+
if (/^(km|kms|quil)/.test(unit)) tokens.push({ kind: "km", value });
|
|
62
|
+
else if (/^(reais|real|conto|pila)/.test(unit)) tokens.push({ kind: "fee", value });
|
|
63
|
+
else if (/^h/.test(unit)) tokens.push({ kind: "eta", value: value * 60 });
|
|
64
|
+
else tokens.push({ kind: "eta", value });
|
|
65
|
+
} else if (g.free !== void 0) tokens.push({ kind: "fee", value: 0 });
|
|
66
|
+
else if (g.emNum !== void 0) {
|
|
67
|
+
const hours = /^h/.test(g.emUnit ?? "");
|
|
68
|
+
tokens.push({ kind: "eta", value: Number(g.emNum) * (hours ? 60 : 1) });
|
|
69
|
+
} else if (g.bare !== void 0) tokens.push({ kind: "num", value: Number(g.bare) });
|
|
70
|
+
}
|
|
71
|
+
return tokens;
|
|
72
|
+
}
|
|
73
|
+
function parseDeliveryZonesText(text) {
|
|
74
|
+
if (!text || !text.trim()) return [];
|
|
75
|
+
const zones = [];
|
|
76
|
+
let current = null;
|
|
77
|
+
let pendingFee;
|
|
78
|
+
let pendingEta;
|
|
79
|
+
for (const tok of tokenize(text)) {
|
|
80
|
+
if (tok.kind === "km") {
|
|
81
|
+
if (current) zones.push(current);
|
|
82
|
+
current = { maxKm: tok.value, fee: pendingFee, etaMinutes: pendingEta };
|
|
83
|
+
pendingFee = void 0;
|
|
84
|
+
pendingEta = void 0;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (tok.kind === "fee") {
|
|
88
|
+
if (current && current.fee === void 0) current.fee = tok.value;
|
|
89
|
+
else pendingFee = tok.value;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (tok.kind === "eta") {
|
|
93
|
+
if (current && current.etaMinutes === void 0) current.etaMinutes = tok.value;
|
|
94
|
+
else if (!current) pendingEta = tok.value;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (current) {
|
|
98
|
+
if (current.fee === void 0) current.fee = tok.value;
|
|
99
|
+
else if (current.etaMinutes === void 0) current.etaMinutes = tok.value;
|
|
100
|
+
} else if (pendingFee === void 0) {
|
|
101
|
+
pendingFee = tok.value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (current) zones.push(current);
|
|
105
|
+
return normalizeZones(zones.filter((z) => z.fee !== void 0));
|
|
106
|
+
}
|
|
107
|
+
function formatFee(fee) {
|
|
108
|
+
if (fee === 0) return "Gr\xE1tis";
|
|
109
|
+
return `R$ ${fee.toFixed(2).replace(".", ",")}`;
|
|
110
|
+
}
|
|
111
|
+
function formatKm(km) {
|
|
112
|
+
return `${String(km).replace(".", ",")} km`;
|
|
113
|
+
}
|
|
114
|
+
function bandLabel(zones, index) {
|
|
115
|
+
const zone = zones[index];
|
|
116
|
+
if (index === 0) return `at\xE9 ${formatKm(zone.maxKm)}`;
|
|
117
|
+
return `${String(zones[index - 1].maxKm).replace(".", ",")} a ${formatKm(zone.maxKm)}`;
|
|
118
|
+
}
|
|
119
|
+
function renderZonesTable(zones) {
|
|
120
|
+
const sorted = normalizeZones(zones);
|
|
121
|
+
if (sorted.length === 0) return "";
|
|
122
|
+
const lines = ["| Dist\xE2ncia | Taxa | Tempo |", "|---|---|---|"];
|
|
123
|
+
sorted.forEach((zone, i) => {
|
|
124
|
+
const eta = zone.etaMinutes ? `${zone.etaMinutes} min` : "\u2014";
|
|
125
|
+
lines.push(`| ${bandLabel(sorted, i)} | ${formatFee(zone.fee)} | ${eta} |`);
|
|
126
|
+
});
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
function describeZones(zones) {
|
|
130
|
+
return normalizeZones(zones).map((z) => `at\xE9 ${formatKm(z.maxKm)} por ${formatFee(z.fee)}`).join(", ");
|
|
131
|
+
}
|
|
132
|
+
function labelZones(zones) {
|
|
133
|
+
const sorted = normalizeZones(zones);
|
|
134
|
+
return sorted.map((z, i) => ({
|
|
135
|
+
...z,
|
|
136
|
+
label: [bandLabel(sorted, i), formatFee(z.fee), z.etaMinutes ? `${z.etaMinutes} min` : null].filter(Boolean).join(" \xB7 ")
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
function shortFee(fee) {
|
|
140
|
+
if (fee === 0) return "gr\xE1tis";
|
|
141
|
+
return Number.isInteger(fee) ? `R$ ${fee}` : formatFee(fee);
|
|
142
|
+
}
|
|
143
|
+
function summarizeZonesShort(zones) {
|
|
144
|
+
const sorted = normalizeZones(zones);
|
|
145
|
+
return sorted.map((z, i) => `${bandLabel(sorted, i)} ${shortFee(z.fee)}${z.etaMinutes ? ` (${z.etaMinutes} min)` : ""}`).join("; ");
|
|
146
|
+
}
|
|
147
|
+
var DELIVERY_ZONES_MIGRATION = "008_delivery_has_zones";
|
|
148
|
+
var DELIVERY_ZONES_MIGRATION_MESSAGE = `A chave delivery.zones ainda n\xE3o existe no servidor \u2014 a migration 008 (${DELIVERY_ZONES_MIGRATION}, plugin-orders) precisa ser aplicada no banco.`;
|
|
149
|
+
var SETTINGS_FORBIDDEN_MESSAGE = "S\xF3 quem gerencia as configura\xE7\xF5es pode gravar a entrega.";
|
|
150
|
+
function errorCode(e) {
|
|
151
|
+
return typeof e === "object" && e && "code" in e ? String(e.code) : void 0;
|
|
152
|
+
}
|
|
153
|
+
function errorMessage(e) {
|
|
154
|
+
if (e instanceof Error) return e.message;
|
|
155
|
+
if (typeof e === "object" && e && "message" in e) return String(e.message);
|
|
156
|
+
return String(e);
|
|
157
|
+
}
|
|
158
|
+
function classifySettingWriteError(e) {
|
|
159
|
+
const raw = errorMessage(e);
|
|
160
|
+
const unknownKey = /unknown key\s+(\S+)/i.exec(raw);
|
|
161
|
+
if (unknownKey) {
|
|
162
|
+
const key = unknownKey[1];
|
|
163
|
+
return {
|
|
164
|
+
reason: "migration_missing",
|
|
165
|
+
message: key === "delivery.zones" ? DELIVERY_ZONES_MIGRATION_MESSAGE : `A chave ${key} ainda n\xE3o existe no servidor \u2014 a migration que a registra precisa ser aplicada no banco.`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (errorCode(e) === "42501" || /settings\.manage/i.test(raw)) return { reason: "forbidden", message: SETTINGS_FORBIDDEN_MESSAGE };
|
|
169
|
+
return { reason: "error", message: raw };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { classifySettingWriteError, describeZones, labelZones, normalizeZones, parseDeliveryZonesText, renderZonesTable, summarizeZonesShort, zonesFromSetting, zonesToSetting };
|
|
173
|
+
//# sourceMappingURL=chunk-AKOF3KHU.js.map
|
|
174
|
+
//# sourceMappingURL=chunk-AKOF3KHU.js.map
|