medusa-plugin-quickink-logistics 0.0.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 (27) hide show
  1. package/.medusa/server/src/admin/index.js +668 -0
  2. package/.medusa/server/src/admin/index.mjs +669 -0
  3. package/.medusa/server/src/api/admin/plugin/route.js +7 -0
  4. package/.medusa/server/src/api/admin/quickink-logistics/pincodes/[id]/route.js +26 -0
  5. package/.medusa/server/src/api/admin/quickink-logistics/pincodes/import/route.js +45 -0
  6. package/.medusa/server/src/api/admin/quickink-logistics/pincodes/pincode/[pincode]/route.js +22 -0
  7. package/.medusa/server/src/api/admin/quickink-logistics/pincodes/route.js +39 -0
  8. package/.medusa/server/src/api/admin/quickink-logistics/pincodes/validators.js +76 -0
  9. package/.medusa/server/src/api/admin/quickink-logistics/pincodes/validators.spec.js +50 -0
  10. package/.medusa/server/src/api/middlewares.js +25 -0
  11. package/.medusa/server/src/api/store/plugin/route.js +7 -0
  12. package/.medusa/server/src/config.js +10 -0
  13. package/.medusa/server/src/index.js +13 -0
  14. package/.medusa/server/src/modules/quickink-logistics/csv.js +88 -0
  15. package/.medusa/server/src/modules/quickink-logistics/csv.spec.js +102 -0
  16. package/.medusa/server/src/modules/quickink-logistics/index.js +15 -0
  17. package/.medusa/server/src/modules/quickink-logistics/migrations/Migration20250321120000CreateQuickinkPincodeTable.js +35 -0
  18. package/.medusa/server/src/modules/quickink-logistics/migrations/Migration20260321180000AddUniquePincodeIndex.js +27 -0
  19. package/.medusa/server/src/modules/quickink-logistics/migrations/Migration20260324120000EnsureQuickinkPincodeUniqueConstraint.js +38 -0
  20. package/.medusa/server/src/modules/quickink-logistics/migrations/Migration20260324170000SetQuickinkPincodeIdDefault.js +25 -0
  21. package/.medusa/server/src/modules/quickink-logistics/migrations/Migration20260325103000QuickinkPincodePartialUniqueActive.js +35 -0
  22. package/.medusa/server/src/modules/quickink-logistics/models/quickink-pincode.js +16 -0
  23. package/.medusa/server/src/modules/quickink-logistics/service.js +183 -0
  24. package/.medusa/server/src/types/pincode.js +3 -0
  25. package/.medusa/server/src/workflows/index.js +3 -0
  26. package/README.md +99 -0
  27. package/package.json +82 -0
@@ -0,0 +1,668 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const react = require("react");
4
+ const reactDom = require("react-dom");
5
+ const adminSdk = require("@medusajs/admin-sdk");
6
+ const ui = require("@medusajs/ui");
7
+ const icons = require("@medusajs/icons");
8
+ const PINCODES_API = "/admin/quickink-logistics/pincodes";
9
+ const IMPORT_API = "/admin/quickink-logistics/pincodes/import";
10
+ const PAGE_SIZE = 50;
11
+ const useDebounce = (value, delay) => {
12
+ const [debounced, setDebounced] = react.useState(value);
13
+ react.useEffect(() => {
14
+ const t = setTimeout(() => setDebounced(value), delay);
15
+ return () => clearTimeout(t);
16
+ }, [value, delay]);
17
+ return debounced;
18
+ };
19
+ const appendTriBool = (params, key, v) => {
20
+ if (v === "all") return;
21
+ params.set(key, v);
22
+ };
23
+ const QikinkServiceabilityPage = () => {
24
+ const [pincodes, setPincodes] = react.useState([]);
25
+ const [count, setCount] = react.useState(0);
26
+ const [page, setPage] = react.useState(0);
27
+ const [isLoading, setIsLoading] = react.useState(true);
28
+ const [error, setError] = react.useState(null);
29
+ const [pincodeSearch, setPincodeSearch] = react.useState("");
30
+ const debouncedPincode = useDebounce(pincodeSearch, 300);
31
+ const [codFilter, setCodFilter] = react.useState("all");
32
+ const [prepaidFilter, setPrepaidFilter] = react.useState("all");
33
+ const [pickupFilter, setPickupFilter] = react.useState("all");
34
+ const [reverseFilter, setReverseFilter] = react.useState("all");
35
+ const [listScope, setListScope] = react.useState(
36
+ "active"
37
+ );
38
+ const [order, setOrder] = react.useState(
39
+ "created_at"
40
+ );
41
+ const [orderDirection, setOrderDirection] = react.useState("DESC");
42
+ const [createOpen, setCreateOpen] = react.useState(false);
43
+ const [editRow, setEditRow] = react.useState(null);
44
+ const fileInputRef = react.useRef(null);
45
+ const loadList = react.useCallback(async () => {
46
+ setIsLoading(true);
47
+ setError(null);
48
+ try {
49
+ const params = new URLSearchParams();
50
+ params.set("limit", String(PAGE_SIZE));
51
+ params.set("offset", String(page * PAGE_SIZE));
52
+ params.set("list_scope", listScope);
53
+ params.set("order", order);
54
+ params.set("order_direction", orderDirection);
55
+ if (debouncedPincode.trim()) {
56
+ params.set("pincode", debouncedPincode.trim());
57
+ }
58
+ appendTriBool(params, "cod_delivery", codFilter);
59
+ appendTriBool(params, "prepaid_delivery", prepaidFilter);
60
+ appendTriBool(params, "pickup", pickupFilter);
61
+ appendTriBool(params, "reverse_pickup", reverseFilter);
62
+ const res = await fetch(`${PINCODES_API}?${params.toString()}`, {
63
+ credentials: "include"
64
+ });
65
+ if (!res.ok) {
66
+ throw new Error(await res.text());
67
+ }
68
+ const data = await res.json();
69
+ setPincodes(data.pincodes ?? []);
70
+ setCount(data.count ?? 0);
71
+ } catch (e) {
72
+ setError(e instanceof Error ? e.message : "Failed to load pincodes");
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ }, [
77
+ page,
78
+ debouncedPincode,
79
+ codFilter,
80
+ prepaidFilter,
81
+ pickupFilter,
82
+ reverseFilter,
83
+ listScope,
84
+ order,
85
+ orderDirection
86
+ ]);
87
+ const filterKey = react.useMemo(
88
+ () => [
89
+ debouncedPincode,
90
+ codFilter,
91
+ prepaidFilter,
92
+ pickupFilter,
93
+ reverseFilter,
94
+ listScope,
95
+ order,
96
+ orderDirection
97
+ ].join("|"),
98
+ [
99
+ debouncedPincode,
100
+ codFilter,
101
+ prepaidFilter,
102
+ pickupFilter,
103
+ reverseFilter,
104
+ listScope,
105
+ order,
106
+ orderDirection
107
+ ]
108
+ );
109
+ react.useLayoutEffect(() => {
110
+ reactDom.flushSync(() => setPage(0));
111
+ }, [filterKey]);
112
+ react.useEffect(() => {
113
+ void loadList();
114
+ }, [loadList]);
115
+ const handleDelete = async (id) => {
116
+ if (!window.confirm("delete pincode entry.")) return;
117
+ try {
118
+ const res = await fetch(`${PINCODES_API}/${id}`, {
119
+ method: "DELETE",
120
+ credentials: "include"
121
+ });
122
+ if (!res.ok) {
123
+ throw new Error(await res.text());
124
+ }
125
+ ui.toast.success("Deleted");
126
+ await loadList();
127
+ } catch (e) {
128
+ ui.toast.error(e instanceof Error ? e.message : "Delete failed");
129
+ }
130
+ };
131
+ const handleImport = async (e) => {
132
+ var _a;
133
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
134
+ e.target.value = "";
135
+ if (!file) return;
136
+ const fd = new FormData();
137
+ fd.append("file", file);
138
+ try {
139
+ const res = await fetch(IMPORT_API, {
140
+ method: "POST",
141
+ body: fd,
142
+ credentials: "include"
143
+ });
144
+ if (!res.ok) {
145
+ const text = await res.text();
146
+ throw new Error(text || "Import failed");
147
+ }
148
+ const data = await res.json();
149
+ ui.toast.success(
150
+ `Import done. Created: ${data.created ?? 0}, updated: ${data.updated ?? 0}`
151
+ );
152
+ await loadList();
153
+ } catch (err) {
154
+ ui.toast.error(err instanceof Error ? err.message : "Import failed");
155
+ }
156
+ };
157
+ const totalPages = Math.max(1, Math.ceil(count / PAGE_SIZE));
158
+ const canPrev = page > 0;
159
+ const canNext = (page + 1) * PAGE_SIZE < count;
160
+ const selectClass = "h-9 min-w-[7rem] rounded-md border border-ui-border-base bg-ui-bg-field px-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-fg-interactive";
161
+ const compactSelectClass = "h-8 w-full min-w-[5.5rem] rounded-md border border-ui-border-base bg-ui-bg-field px-2 text-xs text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-fg-interactive";
162
+ const optionStyle = { color: "#111827", backgroundColor: "#ffffff" };
163
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full p-6", children: [
164
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "mx-auto flex w-full max-w-[1400px] flex-col gap-6 p-6", children: [
165
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-2", children: [
166
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Qikink serviceability" }),
167
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Manage pincodes: import CSV, search, filter, create, edit via dialog, and soft-delete." })
168
+ ] }),
169
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between", children: [
170
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-2 xl:max-w-md", children: [
171
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", className: "text-ui-fg-muted", children: "Search by pincode" }),
172
+ /* @__PURE__ */ jsxRuntime.jsx(
173
+ ui.Input,
174
+ {
175
+ type: "search",
176
+ placeholder: "Pincode contains…",
177
+ value: pincodeSearch,
178
+ onChange: (ev) => setPincodeSearch(ev.target.value)
179
+ }
180
+ )
181
+ ] }),
182
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-end justify-end gap-2", children: [
183
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
184
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Rows" }),
185
+ /* @__PURE__ */ jsxRuntime.jsxs(
186
+ "select",
187
+ {
188
+ className: selectClass,
189
+ value: listScope,
190
+ onChange: (e) => setListScope(e.target.value),
191
+ children: [
192
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "active", children: "Active" }),
193
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "deleted", children: "Deleted" }),
194
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "all", children: "All" })
195
+ ]
196
+ }
197
+ )
198
+ ] }),
199
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
200
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Sort" }),
201
+ /* @__PURE__ */ jsxRuntime.jsxs(
202
+ "select",
203
+ {
204
+ className: selectClass,
205
+ value: order,
206
+ onChange: (e) => setOrder(e.target.value),
207
+ children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "created_at", children: "Created" }),
209
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "updated_at", children: "Updated" }),
210
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "pincode", children: "Pincode" })
211
+ ]
212
+ }
213
+ )
214
+ ] }),
215
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
216
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Dir" }),
217
+ /* @__PURE__ */ jsxRuntime.jsxs(
218
+ "select",
219
+ {
220
+ className: selectClass,
221
+ value: orderDirection,
222
+ onChange: (e) => setOrderDirection(e.target.value),
223
+ children: [
224
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "DESC", children: "Desc" }),
225
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "ASC", children: "Asc" })
226
+ ]
227
+ }
228
+ )
229
+ ] }),
230
+ /* @__PURE__ */ jsxRuntime.jsx(
231
+ "input",
232
+ {
233
+ ref: fileInputRef,
234
+ type: "file",
235
+ accept: ".csv,text/csv",
236
+ className: "hidden",
237
+ onChange: handleImport
238
+ }
239
+ ),
240
+ /* @__PURE__ */ jsxRuntime.jsx(
241
+ ui.Button,
242
+ {
243
+ variant: "secondary",
244
+ type: "button",
245
+ onClick: () => {
246
+ var _a;
247
+ return (_a = fileInputRef.current) == null ? void 0 : _a.click();
248
+ },
249
+ children: "Import CSV"
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "primary", type: "button", onClick: () => setCreateOpen(true), children: "Create" })
253
+ ] })
254
+ ] }),
255
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-error p-4 text-ui-fg-error", children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: error }),
257
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", className: "mt-2", onClick: () => void loadList(), children: "Retry" })
258
+ ] }),
259
+ !error && isLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "py-8 text-center text-ui-fg-muted", children: "Loading…" }),
260
+ !error && !isLoading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
261
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto rounded-xl border border-ui-border-base", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
262
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Header, { children: [
263
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
264
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "id" }),
265
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Pincode" }),
266
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "COD delivery" }),
267
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Prepaid delivery" }),
268
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Pickup" }),
269
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Reverse pickup" }),
270
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Created" }),
271
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Updated" }),
272
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
273
+ ] }),
274
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
275
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, {}),
276
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, {}),
277
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: /* @__PURE__ */ jsxRuntime.jsxs(
278
+ "select",
279
+ {
280
+ className: compactSelectClass,
281
+ value: codFilter,
282
+ onChange: (e) => setCodFilter(e.target.value),
283
+ children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "all", children: "All" }),
285
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "true", children: "Yes" }),
286
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "false", children: "No" })
287
+ ]
288
+ }
289
+ ) }),
290
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: /* @__PURE__ */ jsxRuntime.jsxs(
291
+ "select",
292
+ {
293
+ className: compactSelectClass,
294
+ value: prepaidFilter,
295
+ onChange: (e) => setPrepaidFilter(e.target.value),
296
+ children: [
297
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "all", children: "All" }),
298
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "true", children: "Yes" }),
299
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "false", children: "No" })
300
+ ]
301
+ }
302
+ ) }),
303
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: /* @__PURE__ */ jsxRuntime.jsxs(
304
+ "select",
305
+ {
306
+ className: compactSelectClass,
307
+ value: pickupFilter,
308
+ onChange: (e) => setPickupFilter(e.target.value),
309
+ children: [
310
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "all", children: "All" }),
311
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "true", children: "Yes" }),
312
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "false", children: "No" })
313
+ ]
314
+ }
315
+ ) }),
316
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: /* @__PURE__ */ jsxRuntime.jsxs(
317
+ "select",
318
+ {
319
+ className: compactSelectClass,
320
+ value: reverseFilter,
321
+ onChange: (e) => setReverseFilter(e.target.value),
322
+ children: [
323
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "all", children: "All" }),
324
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "true", children: "Yes" }),
325
+ /* @__PURE__ */ jsxRuntime.jsx("option", { style: optionStyle, value: "false", children: "No" })
326
+ ]
327
+ }
328
+ ) }),
329
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, {}),
330
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, {}),
331
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, {}),
332
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, {})
333
+ ] })
334
+ ] }),
335
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: pincodes.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
336
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No rows match." }) }),
337
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
338
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
339
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
340
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
341
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
342
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
343
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
344
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
345
+ ] }) : pincodes.map((row) => {
346
+ const disabled = row.deleted_at != null;
347
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "font-mono text-ui-fg-muted text-sm", children: row.id }),
349
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "font-medium", children: row.pincode }),
350
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: row.cod_delivery ? "Yes" : "No" }),
351
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: row.prepaid_delivery ? "Yes" : "No" }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: row.pickup ? "Yes" : "No" }),
353
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: row.reverse_pickup ? "Yes" : "No" }),
354
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-ui-fg-muted text-sm whitespace-nowrap", children: new Date(row.created_at).toLocaleString() }),
355
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-ui-fg-muted text-sm whitespace-nowrap", children: new Date(row.updated_at).toLocaleString() }),
356
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-1", children: [
357
+ /* @__PURE__ */ jsxRuntime.jsx(
358
+ ui.IconButton,
359
+ {
360
+ type: "button",
361
+ variant: "transparent",
362
+ disabled,
363
+ onClick: () => setEditRow(row),
364
+ "aria-label": "Edit row",
365
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, {})
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsxRuntime.jsx(
369
+ ui.IconButton,
370
+ {
371
+ type: "button",
372
+ variant: "transparent",
373
+ disabled,
374
+ onClick: () => void handleDelete(row.id),
375
+ "aria-label": "Delete row",
376
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-error" })
377
+ }
378
+ )
379
+ ] }) })
380
+ ] }, row.id);
381
+ }) })
382
+ ] }) }),
383
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t border-ui-border-base pt-4", children: [
384
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-muted", children: [
385
+ "Page ",
386
+ page + 1,
387
+ " of ",
388
+ totalPages,
389
+ " — ",
390
+ count,
391
+ " total"
392
+ ] }),
393
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
394
+ /* @__PURE__ */ jsxRuntime.jsx(
395
+ ui.Button,
396
+ {
397
+ variant: "secondary",
398
+ size: "small",
399
+ disabled: !canPrev,
400
+ onClick: () => setPage((p) => Math.max(0, p - 1)),
401
+ children: "Previous"
402
+ }
403
+ ),
404
+ /* @__PURE__ */ jsxRuntime.jsx(
405
+ ui.Button,
406
+ {
407
+ variant: "secondary",
408
+ size: "small",
409
+ disabled: !canNext,
410
+ onClick: () => setPage((p) => p + 1),
411
+ children: "Next"
412
+ }
413
+ )
414
+ ] })
415
+ ] })
416
+ ] })
417
+ ] }),
418
+ /* @__PURE__ */ jsxRuntime.jsx(
419
+ CreatePincodeModal,
420
+ {
421
+ open: createOpen,
422
+ onOpenChange: setCreateOpen,
423
+ onCreated: () => void loadList()
424
+ }
425
+ ),
426
+ /* @__PURE__ */ jsxRuntime.jsx(
427
+ EditPincodeModal,
428
+ {
429
+ row: editRow,
430
+ onOpenChange: (open) => !open && setEditRow(null),
431
+ onSaved: () => void loadList()
432
+ }
433
+ )
434
+ ] });
435
+ };
436
+ function CreatePincodeModal({ open, onOpenChange, onCreated }) {
437
+ const [pincode, setPincode] = react.useState("");
438
+ const [cod, setCod] = react.useState(false);
439
+ const [prepaid, setPrepaid] = react.useState(false);
440
+ const [pickup, setPickup] = react.useState(false);
441
+ const [reverse, setReverse] = react.useState(false);
442
+ const [saving, setSaving] = react.useState(false);
443
+ react.useEffect(() => {
444
+ if (open) {
445
+ setPincode("");
446
+ setCod(false);
447
+ setPrepaid(false);
448
+ setPickup(false);
449
+ setReverse(false);
450
+ }
451
+ }, [open]);
452
+ const submit = async () => {
453
+ const trimmed = pincode.trim();
454
+ if (!trimmed) {
455
+ ui.toast.error("Pincode is required");
456
+ return;
457
+ }
458
+ setSaving(true);
459
+ try {
460
+ const res = await fetch(PINCODES_API, {
461
+ method: "POST",
462
+ credentials: "include",
463
+ headers: { "Content-Type": "application/json" },
464
+ body: JSON.stringify({
465
+ pincode: trimmed,
466
+ cod_delivery: cod,
467
+ prepaid_delivery: prepaid,
468
+ pickup,
469
+ reverse_pickup: reverse
470
+ })
471
+ });
472
+ if (!res.ok) {
473
+ throw new Error(await res.text());
474
+ }
475
+ ui.toast.success("Created");
476
+ onOpenChange(false);
477
+ onCreated();
478
+ } catch (e) {
479
+ ui.toast.error(e instanceof Error ? e.message : "Create failed");
480
+ } finally {
481
+ setSaving(false);
482
+ }
483
+ };
484
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
485
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Title, { children: "Create pincode" }) }),
486
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Body, { className: "flex flex-col gap-4", children: [
487
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
488
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", className: "mb-1", children: "Pincode" }),
489
+ /* @__PURE__ */ jsxRuntime.jsx(
490
+ ui.Input,
491
+ {
492
+ value: pincode,
493
+ onChange: (e) => setPincode(e.target.value),
494
+ placeholder: "e.g. 560011"
495
+ }
496
+ )
497
+ ] }),
498
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
499
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: cod, onCheckedChange: (v) => setCod(v === true) }),
500
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "COD delivery" })
501
+ ] }),
502
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
503
+ /* @__PURE__ */ jsxRuntime.jsx(
504
+ ui.Checkbox,
505
+ {
506
+ checked: prepaid,
507
+ onCheckedChange: (v) => setPrepaid(v === true)
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Prepaid delivery" })
511
+ ] }),
512
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
513
+ /* @__PURE__ */ jsxRuntime.jsx(
514
+ ui.Checkbox,
515
+ {
516
+ checked: pickup,
517
+ onCheckedChange: (v) => setPickup(v === true)
518
+ }
519
+ ),
520
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Pickup" })
521
+ ] }),
522
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
523
+ /* @__PURE__ */ jsxRuntime.jsx(
524
+ ui.Checkbox,
525
+ {
526
+ checked: reverse,
527
+ onCheckedChange: (v) => setReverse(v === true)
528
+ }
529
+ ),
530
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Reverse pickup" })
531
+ ] })
532
+ ] }),
533
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Footer, { children: [
534
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }),
535
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "primary", onClick: () => void submit(), disabled: saving, children: saving ? "Saving…" : "Create" })
536
+ ] })
537
+ ] }) });
538
+ }
539
+ function EditPincodeModal({ row, onOpenChange, onSaved }) {
540
+ const open = row != null;
541
+ const [cod, setCod] = react.useState(false);
542
+ const [prepaid, setPrepaid] = react.useState(false);
543
+ const [pickup, setPickup] = react.useState(false);
544
+ const [reverse, setReverse] = react.useState(false);
545
+ const [saving, setSaving] = react.useState(false);
546
+ react.useEffect(() => {
547
+ if (row) {
548
+ setCod(row.cod_delivery);
549
+ setPrepaid(row.prepaid_delivery);
550
+ setPickup(row.pickup);
551
+ setReverse(row.reverse_pickup);
552
+ }
553
+ }, [row]);
554
+ const submit = async () => {
555
+ if (!row) return;
556
+ setSaving(true);
557
+ try {
558
+ const res = await fetch(`${PINCODES_API}/${row.id}`, {
559
+ method: "PUT",
560
+ credentials: "include",
561
+ headers: { "Content-Type": "application/json" },
562
+ body: JSON.stringify({
563
+ cod_delivery: cod,
564
+ prepaid_delivery: prepaid,
565
+ pickup,
566
+ reverse_pickup: reverse
567
+ })
568
+ });
569
+ if (!res.ok) {
570
+ throw new Error(await res.text());
571
+ }
572
+ ui.toast.success("Saved");
573
+ onOpenChange(false);
574
+ onSaved();
575
+ } catch (e) {
576
+ ui.toast.error(e instanceof Error ? e.message : "Save failed");
577
+ } finally {
578
+ setSaving(false);
579
+ }
580
+ };
581
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
582
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Title, { children: "Edit pincode" }) }),
583
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Body, { className: "flex flex-col gap-4", children: [
584
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
585
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", className: "mb-1", children: "Pincode (read-only)" }),
586
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "rounded-md border border-ui-border-base bg-ui-bg-subtle px-3 py-2", children: (row == null ? void 0 : row.pincode) ?? "—" })
587
+ ] }),
588
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
589
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: cod, onCheckedChange: (v) => setCod(v === true) }),
590
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "COD delivery" })
591
+ ] }),
592
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ ui.Checkbox,
595
+ {
596
+ checked: prepaid,
597
+ onCheckedChange: (v) => setPrepaid(v === true)
598
+ }
599
+ ),
600
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Prepaid delivery" })
601
+ ] }),
602
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
603
+ /* @__PURE__ */ jsxRuntime.jsx(
604
+ ui.Checkbox,
605
+ {
606
+ checked: pickup,
607
+ onCheckedChange: (v) => setPickup(v === true)
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Pickup" })
611
+ ] }),
612
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
613
+ /* @__PURE__ */ jsxRuntime.jsx(
614
+ ui.Checkbox,
615
+ {
616
+ checked: reverse,
617
+ onCheckedChange: (v) => setReverse(v === true)
618
+ }
619
+ ),
620
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: "Reverse pickup" })
621
+ ] })
622
+ ] }),
623
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Footer, { children: [
624
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }),
625
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "primary", onClick: () => void submit(), disabled: saving || !row, children: saving ? "Saving…" : "Save" })
626
+ ] })
627
+ ] }) });
628
+ }
629
+ const config = adminSdk.defineRouteConfig({
630
+ label: "Qikink serviceability",
631
+ icon: icons.PencilSquare
632
+ });
633
+ const i18nTranslations0 = {};
634
+ const widgetModule = { widgets: [] };
635
+ const routeModule = {
636
+ routes: [
637
+ {
638
+ Component: QikinkServiceabilityPage,
639
+ path: "/qikink-serviceability"
640
+ }
641
+ ]
642
+ };
643
+ const menuItemModule = {
644
+ menuItems: [
645
+ {
646
+ label: config.label,
647
+ icon: config.icon,
648
+ path: "/qikink-serviceability",
649
+ nested: void 0,
650
+ rank: void 0,
651
+ translationNs: void 0
652
+ }
653
+ ]
654
+ };
655
+ const formModule = { customFields: {} };
656
+ const displayModule = {
657
+ displays: {}
658
+ };
659
+ const i18nModule = { resources: i18nTranslations0 };
660
+ const plugin = {
661
+ widgetModule,
662
+ routeModule,
663
+ menuItemModule,
664
+ formModule,
665
+ displayModule,
666
+ i18nModule
667
+ };
668
+ module.exports = plugin;