@cardinali-studio/adminkit 1.0.0

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 (181) hide show
  1. package/dist/api.d.ts +195 -0
  2. package/dist/api.js +118 -0
  3. package/dist/app/App.d.ts +3 -0
  4. package/dist/app/App.js +320 -0
  5. package/dist/app/app.css +2068 -0
  6. package/dist/app/index.d.ts +18 -0
  7. package/dist/app/index.js +33 -0
  8. package/dist/app/lib/bookings.d.ts +132 -0
  9. package/dist/app/lib/bookings.js +79 -0
  10. package/dist/app/lib/deliveries.d.ts +95 -0
  11. package/dist/app/lib/deliveries.js +104 -0
  12. package/dist/app/lib/delivery-drop.d.ts +1 -0
  13. package/dist/app/lib/delivery-drop.js +72 -0
  14. package/dist/app/lib/delivery-upload.d.ts +78 -0
  15. package/dist/app/lib/delivery-upload.js +585 -0
  16. package/dist/app/lib/groups.d.ts +9 -0
  17. package/dist/app/lib/groups.js +32 -0
  18. package/dist/app/lib/home.d.ts +11 -0
  19. package/dist/app/lib/home.js +67 -0
  20. package/dist/app/lib/overview.d.ts +82 -0
  21. package/dist/app/lib/overview.js +68 -0
  22. package/dist/app/lib/pages.d.ts +24 -0
  23. package/dist/app/lib/pages.js +156 -0
  24. package/dist/app/lib/publish.d.ts +18 -0
  25. package/dist/app/lib/publish.js +63 -0
  26. package/dist/app/lib/reorder.d.ts +3 -0
  27. package/dist/app/lib/reorder.js +31 -0
  28. package/dist/app/lib/requests.d.ts +10 -0
  29. package/dist/app/lib/requests.js +67 -0
  30. package/dist/app/lib/router.d.ts +1 -0
  31. package/dist/app/lib/router.js +14 -0
  32. package/dist/app/lib/settings.d.ts +29 -0
  33. package/dist/app/lib/settings.js +82 -0
  34. package/dist/app/lib/slug.d.ts +3 -0
  35. package/dist/app/lib/slug.js +23 -0
  36. package/dist/app/lib/text.d.ts +1 -0
  37. package/dist/app/lib/text.js +13 -0
  38. package/dist/app/lib/theme.d.ts +7 -0
  39. package/dist/app/lib/theme.js +43 -0
  40. package/dist/app/lib/upload-queue.d.ts +35 -0
  41. package/dist/app/lib/upload-queue.js +167 -0
  42. package/dist/app/routes/AboutEditor.d.ts +15 -0
  43. package/dist/app/routes/AboutEditor.js +122 -0
  44. package/dist/app/routes/Bookings.d.ts +21 -0
  45. package/dist/app/routes/Bookings.js +134 -0
  46. package/dist/app/routes/Deliveries.d.ts +37 -0
  47. package/dist/app/routes/Deliveries.js +563 -0
  48. package/dist/app/routes/GallerySectionsEditor.d.ts +12 -0
  49. package/dist/app/routes/GallerySectionsEditor.js +107 -0
  50. package/dist/app/routes/HomeEditor.d.ts +10 -0
  51. package/dist/app/routes/HomeEditor.js +188 -0
  52. package/dist/app/routes/Overview.d.ts +17 -0
  53. package/dist/app/routes/Overview.js +318 -0
  54. package/dist/app/routes/PageActions.d.ts +12 -0
  55. package/dist/app/routes/PageActions.js +93 -0
  56. package/dist/app/routes/PageEditor.d.ts +23 -0
  57. package/dist/app/routes/PageEditor.js +233 -0
  58. package/dist/app/routes/PageSeoEditor.d.ts +13 -0
  59. package/dist/app/routes/PageSeoEditor.js +102 -0
  60. package/dist/app/routes/PagesList.d.ts +10 -0
  61. package/dist/app/routes/PagesList.js +76 -0
  62. package/dist/app/routes/PortfolioWorksEditor.d.ts +12 -0
  63. package/dist/app/routes/PortfolioWorksEditor.js +139 -0
  64. package/dist/app/routes/Requests.d.ts +13 -0
  65. package/dist/app/routes/Requests.js +30 -0
  66. package/dist/app/routes/SiteSettingsEditor.d.ts +21 -0
  67. package/dist/app/routes/SiteSettingsEditor.js +66 -0
  68. package/dist/app/routes/UploadDock.d.ts +1 -0
  69. package/dist/app/routes/UploadDock.js +66 -0
  70. package/dist/app/routes/WorkshopEditionsEditor.d.ts +12 -0
  71. package/dist/app/routes/WorkshopEditionsEditor.js +101 -0
  72. package/dist/app/site.d.ts +37 -0
  73. package/dist/app/site.js +18 -0
  74. package/dist/app/ui/MediaSlot.d.ts +10 -0
  75. package/dist/app/ui/MediaSlot.js +20 -0
  76. package/dist/app/ui/OpenCard.d.ts +18 -0
  77. package/dist/app/ui/OpenCard.js +20 -0
  78. package/dist/app/ui/ParagraphEditor.d.ts +8 -0
  79. package/dist/app/ui/ParagraphEditor.js +37 -0
  80. package/dist/app/ui/dnd.d.ts +1 -0
  81. package/dist/app/ui/dnd.js +11 -0
  82. package/dist/app/ui/icons.d.ts +26 -0
  83. package/dist/app/ui/icons.js +45 -0
  84. package/dist/config.d.ts +5 -0
  85. package/dist/config.js +26 -0
  86. package/dist/form/MediaBrowser.d.ts +13 -0
  87. package/dist/form/MediaBrowser.js +24 -0
  88. package/dist/form/MediaPicker.d.ts +10 -0
  89. package/dist/form/MediaPicker.js +20 -0
  90. package/dist/form/SchemaForm.d.ts +21 -0
  91. package/dist/form/SchemaForm.js +67 -0
  92. package/dist/form/Trash.d.ts +1 -0
  93. package/dist/form/Trash.js +42 -0
  94. package/dist/form/fields/index.d.ts +1 -0
  95. package/dist/form/fields/index.js +110 -0
  96. package/dist/form/fields/objectList.d.ts +1 -0
  97. package/dist/form/fields/objectList.js +99 -0
  98. package/dist/form/index.d.ts +8 -0
  99. package/dist/form/index.js +8 -0
  100. package/dist/form/media/Lightbox.d.ts +4 -0
  101. package/dist/form/media/Lightbox.js +70 -0
  102. package/dist/form/media/MediaFolders.d.ts +20 -0
  103. package/dist/form/media/MediaFolders.js +116 -0
  104. package/dist/form/media/MediaGrid.d.ts +8 -0
  105. package/dist/form/media/MediaGrid.js +139 -0
  106. package/dist/form/media/UploadDialog.d.ts +4 -0
  107. package/dist/form/media/UploadDialog.js +37 -0
  108. package/dist/form/media/useMediaLibrary.d.ts +55 -0
  109. package/dist/form/media/useMediaLibrary.js +495 -0
  110. package/dist/form/prose/ProseBar.d.ts +21 -0
  111. package/dist/form/prose/ProseBar.js +111 -0
  112. package/dist/form/prose/ProseField.d.ts +11 -0
  113. package/dist/form/prose/ProseField.js +152 -0
  114. package/dist/form/prose/pages.d.ts +6 -0
  115. package/dist/form/prose/pages.js +13 -0
  116. package/dist/form/registry.d.ts +20 -0
  117. package/dist/form/registry.js +15 -0
  118. package/dist/frontmatter.d.ts +20 -0
  119. package/dist/frontmatter.js +202 -0
  120. package/dist/functions/_middleware.js +101 -0
  121. package/dist/functions/_shared/analytics-ga4.js +248 -0
  122. package/dist/functions/_shared/analytics-umami.js +119 -0
  123. package/dist/functions/_shared/assets.js +90 -0
  124. package/dist/functions/_shared/b2.js +300 -0
  125. package/dist/functions/_shared/delivery-crypto.js +99 -0
  126. package/dist/functions/_shared/delivery-purge.js +222 -0
  127. package/dist/functions/_shared/edge-cache.js +37 -0
  128. package/dist/functions/_shared/exif.js +53 -0
  129. package/dist/functions/_shared/github.js +138 -0
  130. package/dist/functions/_shared/imagesize.js +101 -0
  131. package/dist/functions/_shared/media-content.js +200 -0
  132. package/dist/functions/_shared/pending-media.js +98 -0
  133. package/dist/functions/_shared/refs.js +155 -0
  134. package/dist/functions/api/analytics.js +106 -0
  135. package/dist/functions/api/assets/delete.js +241 -0
  136. package/dist/functions/api/assets/folder.js +43 -0
  137. package/dist/functions/api/assets/folders.js +32 -0
  138. package/dist/functions/api/assets/move.js +253 -0
  139. package/dist/functions/api/assets/preview.js +17 -0
  140. package/dist/functions/api/assets/trash/delete.js +13 -0
  141. package/dist/functions/api/assets/trash/empty.js +16 -0
  142. package/dist/functions/api/assets/trash/restore.js +43 -0
  143. package/dist/functions/api/assets/trash.js +28 -0
  144. package/dist/functions/api/assets/upload.js +54 -0
  145. package/dist/functions/api/assets/usage.js +41 -0
  146. package/dist/functions/api/assets.js +94 -0
  147. package/dist/functions/api/deliveries.js +629 -0
  148. package/dist/functions/api/delivery/hero.js +21 -0
  149. package/dist/functions/api/delivery/image.js +35 -0
  150. package/dist/functions/api/delivery/multipart.js +261 -0
  151. package/dist/functions/api/pages/get.js +26 -0
  152. package/dist/functions/api/pages.js +354 -0
  153. package/dist/functions/api/publish.js +134 -0
  154. package/dist/functions/api/requests.js +57 -0
  155. package/dist/functions/api/settings.js +236 -0
  156. package/dist/functions/api/singleton.js +105 -0
  157. package/dist/functions/api/storage.js +125 -0
  158. package/dist/functions/index.js +124 -0
  159. package/dist/functions/site.js +38 -0
  160. package/dist/gallery.d.ts +28 -0
  161. package/dist/gallery.js +18 -0
  162. package/dist/index.d.ts +10 -0
  163. package/dist/index.js +10 -0
  164. package/dist/model.d.ts +2 -0
  165. package/dist/model.js +47 -0
  166. package/dist/normalize.d.ts +50 -0
  167. package/dist/normalize.js +120 -0
  168. package/dist/preview.d.ts +3 -0
  169. package/dist/preview.js +82 -0
  170. package/dist/prose.d.ts +17 -0
  171. package/dist/prose.js +205 -0
  172. package/dist/registries.d.ts +43 -0
  173. package/dist/registries.js +29 -0
  174. package/dist/serialize.d.ts +26 -0
  175. package/dist/serialize.js +136 -0
  176. package/dist/testing.d.ts +26 -0
  177. package/dist/testing.js +66 -0
  178. package/dist/theme.css +636 -0
  179. package/dist/yaml.d.ts +8 -0
  180. package/dist/yaml.js +90 -0
  181. package/package.json +97 -0
@@ -0,0 +1,134 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ const days = ["Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato", "Domenica"];
4
+ const statusLabel = {
5
+ pending: "Da approvare", confirming: "Sincronizzo", confirmed: "Confermato",
6
+ rejected: "Rifiutato", cancelled: "Annullato", completed: "Completato",
7
+ no_show: "Non presentato", expired: "Scaduto", anonymized: "Anonimizzato",
8
+ };
9
+ const local = (iso) => new Intl.DateTimeFormat("it-IT", {
10
+ dateStyle: "medium", timeStyle: "short",
11
+ }).format(new Date(iso));
12
+ const time = (minute) => `${String(Math.floor(minute / 60)).padStart(2, "0")}:${String(minute % 60).padStart(2, "0")}`;
13
+ const minute = (value) => {
14
+ const [hours, minutes] = value.split(":").map(Number);
15
+ return hours * 60 + minutes;
16
+ };
17
+ export function BookingsList({ rows, current, onOpen, onAgenda, onSettings, }) {
18
+ return _jsxs("div", { className: "bk-list", children: [_jsxs("button", { className: "col-item" + (!current ? " is-on" : ""), onClick: onAgenda, children: [_jsx("strong", { children: "Agenda" }), _jsx("span", { children: "Tutti gli appuntamenti" })] }), _jsx("div", { className: "bk-list-label", children: "Richieste e prossimi" }), rows.filter((row) => ["pending", "confirming", "confirmed"].includes(row.status)).map((row) => _jsxs("button", { className: "col-item bk-list-item" + (current === row.id ? " is-on" : ""), onClick: () => onOpen(row.id), children: [_jsx("span", { className: `bk-dot is-${row.status}` }), _jsxs("span", { children: [_jsx("strong", { children: row.visitorName || "Dati rimossi" }), _jsxs("small", { children: [local(row.startAt), " \u00B7 ", row.typeName] })] })] }, row.id)), _jsxs("button", { className: "col-item bk-settings-link" + (current === "settings" ? " is-on" : ""), onClick: onSettings, children: [_jsx("strong", { children: "Impostazioni" }), _jsx("span", { children: "Tipologie, orari e Google" })] })] });
19
+ }
20
+ export function BookingAgenda({ store, onOpen }) {
21
+ const [showManual, setShowManual] = useState(false);
22
+ const [showBlock, setShowBlock] = useState(false);
23
+ const [query, setQuery] = useState("");
24
+ const [status, setStatus] = useState("active");
25
+ const [range, setRange] = useState("month");
26
+ const upcoming = useMemo(() => {
27
+ const now = Date.now();
28
+ const until = now + (range === "week" ? 7 : 42) * 86_400_000;
29
+ const needle = query.trim().toLowerCase();
30
+ return (store.rows || []).filter((row) => {
31
+ const starts = Date.parse(row.startAt);
32
+ const inRange = range === "past"
33
+ ? starts < now && starts >= now - 365 * 86_400_000
34
+ : starts >= now && starts < until;
35
+ const inStatus = status === "all" ? true : status === "active"
36
+ ? ["pending", "confirming", "confirmed"].includes(row.status)
37
+ : row.status === status;
38
+ const matches = !needle || [row.visitorName, row.visitorEmail, row.referenceCode, row.typeName]
39
+ .some((value) => String(value || "").toLowerCase().includes(needle));
40
+ return inRange && inStatus && matches;
41
+ });
42
+ }, [query, range, status, store.rows]);
43
+ return _jsxs("div", { className: "bk-page", children: [_jsxs("div", { className: "bk-hero", children: [_jsxs("div", { children: [_jsx("p", { className: "bk-kicker", children: "Agenda" }), _jsxs("h2", { children: [store.pending, " da approvare", _jsx("br", {}), store.upcoming, " in arrivo"] })] }), _jsxs("div", { className: "bk-actions", children: [_jsx("a", { className: "ak-btn", href: "/admin/api/booking/export", children: "Esporta CSV" }), _jsx("button", { className: "ak-btn", onClick: () => setShowBlock((value) => !value), children: "Blocca orario" }), _jsx("button", { className: "ak-btn ak-btn-primary", onClick: () => setShowManual((value) => !value), children: "Nuovo appuntamento" })] })] }), showManual && _jsx(ManualBooking, { store: store, onCreated: (id) => { setShowManual(false); if (id)
44
+ onOpen(id); } }), showBlock && _jsx(ManualBlock, { store: store, onDone: () => setShowBlock(false) }), store.blocks.length > 0 && _jsxs("section", { className: "bk-section", children: [_jsx("h3", { children: "Blocchi manuali" }), _jsx("div", { className: "bk-blocks", children: store.blocks.map((block) => _jsxs("div", { children: [_jsxs("span", { children: [local(block.start_at), " \u2192 ", local(block.end_at), block.note ? ` · ${block.note}` : ""] }), _jsx("button", { onClick: () => void store.removeBlock(block.id), children: "Rimuovi" })] }, block.id)) })] }), _jsxs("section", { className: "bk-section", children: [_jsxs("div", { className: "bk-filter-head", children: [_jsx("h3", { children: "Prossimi appuntamenti" }), _jsxs("div", { className: "bk-filters", children: [_jsx("input", { type: "search", placeholder: "Cerca nome, email o codice", value: query, onChange: (e) => setQuery(e.target.value) }), _jsxs("select", { value: status, onChange: (e) => setStatus(e.target.value), children: [_jsx("option", { value: "active", children: "Attivi" }), _jsx("option", { value: "pending", children: "Da approvare" }), _jsx("option", { value: "confirmed", children: "Confermati" }), _jsx("option", { value: "all", children: "Tutti gli stati" })] }), _jsxs("select", { value: range, onChange: (e) => setRange(e.target.value), children: [_jsx("option", { value: "week", children: "7 giorni" }), _jsx("option", { value: "month", children: "6 settimane" }), _jsx("option", { value: "past", children: "Ultimi 12 mesi" })] })] })] }), upcoming.length ? _jsx("div", { className: "bk-table", children: upcoming.map((row) => _jsxs("button", { onClick: () => onOpen(row.id), children: [_jsx("time", { children: local(row.startAt) }), _jsx("strong", { children: row.visitorName }), _jsx("span", { children: row.typeName }), _jsx("em", { className: `bk-status is-${row.status}`, children: statusLabel[row.status] })] }, row.id)) }) : _jsx("p", { className: "ak-hint", children: "Nessun appuntamento in arrivo." })] })] });
45
+ }
46
+ function ManualBlock({ store, onDone }) {
47
+ const [value, setValue] = useState({ startAt: "", endAt: "", note: "" });
48
+ const submit = async (event) => {
49
+ event.preventDefault();
50
+ await store.addBlock({
51
+ startAt: new Date(value.startAt).toISOString(),
52
+ endAt: new Date(value.endAt).toISOString(),
53
+ note: value.note,
54
+ }).then(onDone).catch(() => undefined);
55
+ };
56
+ return _jsxs("form", { className: "bk-manual", onSubmit: submit, children: [_jsx("h3", { children: "Blocca una fascia" }), _jsxs("label", { children: ["Da", _jsx("input", { type: "datetime-local", required: true, value: value.startAt, onChange: (e) => setValue({ ...value, startAt: e.target.value }) })] }), _jsxs("label", { children: ["A", _jsx("input", { type: "datetime-local", required: true, value: value.endAt, onChange: (e) => setValue({ ...value, endAt: e.target.value }) })] }), _jsxs("label", { children: ["Motivo", _jsx("input", { value: value.note, onChange: (e) => setValue({ ...value, note: e.target.value }) })] }), _jsx("button", { className: "ak-btn ak-btn-primary", disabled: store.busy, children: "Aggiungi blocco" })] });
57
+ }
58
+ function ManualBooking({ store, onCreated }) {
59
+ const [value, setValue] = useState({
60
+ appointmentType: store.types[0]?.slug || "", mode: "in_person",
61
+ startAt: "", name: "", email: "", phone: "", notes: "",
62
+ });
63
+ const submit = async (event) => {
64
+ event.preventDefault();
65
+ const id = await store.createManual({ ...value, startAt: new Date(value.startAt).toISOString() }).catch(() => null);
66
+ onCreated(id);
67
+ };
68
+ return _jsxs("form", { className: "bk-manual", onSubmit: submit, children: [_jsx("h3", { children: "Inserisci un appuntamento" }), _jsxs("label", { children: ["Tipologia", _jsx("select", { value: value.appointmentType, onChange: (e) => setValue({ ...value, appointmentType: e.target.value }), children: store.types.filter((type) => type.active).map((type) => _jsx("option", { value: type.slug, children: type.name }, type.id)) })] }), _jsxs("label", { children: ["Modalit\u00E0", _jsxs("select", { value: value.mode, onChange: (e) => setValue({ ...value, mode: e.target.value }), children: [_jsx("option", { value: "in_person", children: "In presenza" }), _jsx("option", { value: "virtual", children: "Virtuale" })] })] }), _jsxs("label", { children: ["Data e ora", _jsx("input", { type: "datetime-local", required: true, value: value.startAt, onChange: (e) => setValue({ ...value, startAt: e.target.value }) })] }), _jsxs("label", { children: ["Nome", _jsx("input", { required: true, value: value.name, onChange: (e) => setValue({ ...value, name: e.target.value }) })] }), _jsxs("label", { children: ["Email", _jsx("input", { type: "email", required: true, value: value.email, onChange: (e) => setValue({ ...value, email: e.target.value }) })] }), _jsxs("label", { children: ["Telefono", _jsx("input", { value: value.phone, onChange: (e) => setValue({ ...value, phone: e.target.value }) })] }), _jsxs("label", { className: "is-wide", children: ["Note", _jsx("textarea", { value: value.notes, onChange: (e) => setValue({ ...value, notes: e.target.value }) })] }), _jsx("button", { className: "ak-btn ak-btn-primary", disabled: store.busy, children: "Salva come richiesta" })] });
69
+ }
70
+ export function BookingDetail({ row, store }) {
71
+ const [notes, setNotes] = useState(row.adminNotes || "");
72
+ const [newStart, setNewStart] = useState("");
73
+ useEffect(() => setNotes(row.adminNotes || ""), [row.id, row.adminNotes]);
74
+ const place = row.mode === "virtual" ? (row.meetUrl || "Google Meet dopo l’approvazione") : row.location;
75
+ return _jsxs("div", { className: "bk-page bk-detail", children: [_jsxs("div", { className: "bk-detail-head", children: [_jsxs("div", { children: [_jsx("p", { className: "bk-kicker", children: row.referenceCode }), _jsx("h2", { children: row.visitorName || "Dati rimossi" })] }), _jsx("span", { className: `bk-status is-${row.status}`, children: statusLabel[row.status] })] }), _jsxs("dl", { className: "bk-facts", children: [_jsxs("div", { children: [_jsx("dt", { children: "Quando" }), _jsxs("dd", { children: [local(row.startAt), " \u00B7 ", row.durationMinutes, " min"] })] }), _jsxs("div", { children: [_jsx("dt", { children: "Tipologia" }), _jsx("dd", { children: row.typeName })] }), _jsxs("div", { children: [_jsx("dt", { children: "Modalit\u00E0" }), _jsxs("dd", { children: [row.mode === "virtual" ? "Videochiamata" : "In presenza", " \u00B7 ", place] })] }), _jsxs("div", { children: [_jsx("dt", { children: "Contatto" }), _jsxs("dd", { children: [_jsx("a", { href: `mailto:${row.visitorEmail}`, children: row.visitorEmail }), row.visitorPhone ? ` · ${row.visitorPhone}` : ""] })] }), _jsxs("div", { children: [_jsx("dt", { children: "Fuso visitatore" }), _jsx("dd", { children: row.visitorTimezone })] }), _jsxs("div", { children: [_jsx("dt", { children: "Sincronizzazione" }), _jsxs("dd", { children: [row.calendarSyncStatus, row.googleEventUrl ? _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { href: row.googleEventUrl, target: "_blank", children: "Apri evento" })] }) : null] })] })] }), row.visitorNotes && _jsxs("section", { className: "bk-note", children: [_jsx("h3", { children: "Note del visitatore" }), _jsx("p", { children: row.visitorNotes })] }), _jsxs("section", { className: "bk-note", children: [_jsx("h3", { children: "Note interne" }), _jsx("textarea", { value: notes, onChange: (e) => setNotes(e.target.value), rows: 4 }), _jsx("button", { className: "ak-btn", disabled: store.busy, onClick: () => void store.action(row.id, { action: "notes", notes }), children: "Salva note" })] }), ["pending", "confirmed"].includes(row.status) && _jsxs("section", { className: "bk-reschedule", children: [_jsx("h3", { children: "Sposta appuntamento" }), _jsx("input", { type: "datetime-local", value: newStart, onChange: (e) => setNewStart(e.target.value) }), _jsx("button", { className: "ak-btn", disabled: !newStart || store.busy, onClick: () => void store.action(row.id, { action: "reschedule", startAt: new Date(newStart).toISOString() }), children: "Aggiorna data" })] }), _jsxs("div", { className: "bk-actions bk-detail-actions", children: [row.status === "pending" && _jsxs(_Fragment, { children: [_jsx("button", { className: "ak-btn ak-btn-primary", disabled: store.busy, onClick: () => void store.action(row.id, { action: "approve" }), children: "Approva e crea evento" }), _jsx("button", { className: "ak-btn is-danger", disabled: store.busy, onClick: () => void store.action(row.id, { action: "reject" }), children: "Rifiuta" })] }), row.status === "confirmed" && _jsxs(_Fragment, { children: [_jsx("button", { className: "ak-btn", disabled: store.busy, onClick: () => void store.action(row.id, { action: "complete" }), children: "Completato" }), _jsx("button", { className: "ak-btn", disabled: store.busy, onClick: () => void store.action(row.id, { action: "no_show" }), children: "Non presentato" }), _jsx("button", { className: "ak-btn is-danger", disabled: store.busy, onClick: () => void store.action(row.id, { action: "cancel" }), children: "Annulla" })] })] })] });
76
+ }
77
+ export function BookingSettingsHub({ store }) {
78
+ const [tab, setTab] = useState("general");
79
+ const tabs = [["general", "Generali"], ["types", "Tipologie"], ["availability", "Disponibilità"], ["google", "Google"], ["publish", "Attivazione"]];
80
+ return _jsxs("div", { className: "bk-page", children: [_jsx("div", { className: "bk-tabs", children: tabs.map(([id, label]) => _jsx("button", { className: tab === id ? "is-on" : "", onClick: () => setTab(id), children: label }, id)) }), tab === "general" && store.settings && _jsx(GeneralSettings, { value: store.settings, store: store }), tab === "types" && _jsx(TypesSettings, { types: store.types, store: store }), tab === "availability" && _jsx(AvailabilitySettings, { store: store }), tab === "google" && _jsx(GoogleSettings, { store: store }), tab === "publish" && _jsx(ActivationSettings, { store: store })] });
81
+ }
82
+ function GeneralSettings({ value, store }) {
83
+ const [draft, setDraft] = useState(value);
84
+ useEffect(() => setDraft(value), [value]);
85
+ const number = (key, value) => setDraft({ ...draft, [key]: Number(value) });
86
+ return _jsxs("form", { className: "bk-settings-form", onSubmit: (e) => { e.preventDefault(); void store.saveSettings(draft); }, children: [_jsxs("header", { children: [_jsx("p", { className: "bk-kicker", children: "Regole globali" }), _jsx("h2", { children: "Il perimetro dell\u2019agenda." })] }), _jsxs("label", { children: ["Fuso del proprietario", _jsx("input", { value: draft.timezone, onChange: (e) => setDraft({ ...draft, timezone: e.target.value }) })] }), _jsxs("label", { className: "is-wide", children: ["Indirizzo unico", _jsx("input", { value: draft.address, onChange: (e) => setDraft({ ...draft, address: e.target.value }) })] }), _jsxs("label", { children: ["Email proprietario", _jsx("input", { type: "email", value: draft.contactEmail, onChange: (e) => setDraft({ ...draft, contactEmail: e.target.value }) })] }), _jsxs("label", { children: ["Telefono proprietario", _jsx("input", { value: draft.contactPhone, onChange: (e) => setDraft({ ...draft, contactPhone: e.target.value }) })] }), _jsx(NumberField, { label: "Durata predefinita (min)", value: draft.defaultDurationMinutes, onChange: (v) => number("defaultDurationMinutes", v) }), _jsx(NumberField, { label: "Buffer prima (min)", value: draft.defaultBufferBeforeMinutes, onChange: (v) => number("defaultBufferBeforeMinutes", v) }), _jsx(NumberField, { label: "Buffer dopo (min)", value: draft.defaultBufferAfterMinutes, onChange: (v) => number("defaultBufferAfterMinutes", v) }), _jsx(NumberField, { label: "Passo slot (min)", value: draft.slotStepMinutes, onChange: (v) => number("slotStepMinutes", v) }), _jsx(NumberField, { label: "Preavviso minimo (min)", value: draft.minimumNoticeMinutes, onChange: (v) => number("minimumNoticeMinutes", v) }), _jsx(NumberField, { label: "Finestra massima (giorni)", value: draft.bookingHorizonDays, onChange: (v) => number("bookingHorizonDays", v) }), _jsx(NumberField, { label: "Limite al giorno (0 = nessuno)", value: draft.dailyLimit, onChange: (v) => number("dailyLimit", v) }), _jsx(NumberField, { label: "Limite alla settimana (0 = nessuno)", value: draft.weeklyLimit, onChange: (v) => number("weeklyLimit", v) }), _jsx(NumberField, { label: "Primo promemoria (min)", value: draft.reminder1Minutes, onChange: (v) => number("reminder1Minutes", v) }), _jsx(NumberField, { label: "Secondo promemoria (min)", value: draft.reminder2Minutes, onChange: (v) => number("reminder2Minutes", v) }), _jsx(NumberField, { label: "Retention (giorni)", value: draft.retentionDays, onChange: (v) => number("retentionDays", v) }), _jsx("button", { className: "ak-btn ak-btn-primary", disabled: store.busy, children: "Salva impostazioni" })] });
87
+ }
88
+ function NumberField({ label, value, onChange }) {
89
+ return _jsxs("label", { children: [label, _jsx("input", { type: "number", min: "0", value: value, onChange: (e) => onChange(e.target.value) })] });
90
+ }
91
+ function emptyType(order) {
92
+ return { slug: "", name: "", description: "", active: true, durationMinutes: null, bufferBeforeMinutes: null, bufferAfterMinutes: null, modePolicy: "both", sortOrder: order, adminColor: "#e63329" };
93
+ }
94
+ function TypesSettings({ types, store }) {
95
+ const [draft, setDraft] = useState(types);
96
+ useEffect(() => setDraft(types), [types]);
97
+ return _jsxs("section", { className: "bk-settings-section", children: [_jsxs("header", { children: [_jsx("p", { className: "bk-kicker", children: "Offerta" }), _jsx("h2", { children: "Tipologie di appuntamento." })] }), _jsx("div", { className: "bk-types", children: draft.map((type, index) => _jsxs("div", { className: "bk-type", children: [_jsx("input", { className: "bk-type-name", placeholder: "Nome", value: type.name || "", onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, name: e.target.value } : v)) }), _jsx("input", { placeholder: "slug-pubblico", value: type.slug || "", onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, slug: e.target.value } : v)) }), _jsx("textarea", { placeholder: "Descrizione", value: type.description || "", onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, description: e.target.value } : v)) }), _jsxs("label", { children: ["Modalit\u00E0", _jsxs("select", { value: type.modePolicy, onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, modePolicy: e.target.value } : v)), children: [_jsx("option", { value: "both", children: "Scelta del visitatore" }), _jsx("option", { value: "in_person", children: "Solo in presenza" }), _jsx("option", { value: "virtual", children: "Solo virtuale" })] })] }), _jsxs("label", { children: ["Durata, vuoto = default", _jsx("input", { type: "number", value: type.durationMinutes ?? "", onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, durationMinutes: e.target.value ? Number(e.target.value) : null } : v)) })] }), _jsxs("label", { children: ["Buffer prima", _jsx("input", { type: "number", value: type.bufferBeforeMinutes ?? "", onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, bufferBeforeMinutes: e.target.value ? Number(e.target.value) : null } : v)) })] }), _jsxs("label", { children: ["Buffer dopo", _jsx("input", { type: "number", value: type.bufferAfterMinutes ?? "", onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, bufferAfterMinutes: e.target.value ? Number(e.target.value) : null } : v)) })] }), _jsxs("label", { className: "bk-check", children: [_jsx("input", { type: "checkbox", checked: type.active !== false, onChange: (e) => setDraft(draft.map((v, i) => i === index ? { ...v, active: e.target.checked } : v)) }), " Attiva"] }), _jsxs("div", { className: "bk-actions", children: [_jsx("button", { className: "ak-btn ak-btn-primary", onClick: () => void store.saveType(type, !type.id), children: "Salva" }), type.id && _jsx("button", { className: "ak-btn is-danger", onClick: () => void store.removeType(type.id), children: "Archivia" })] })] }, type.id || `new-${index}`)) }), _jsx("button", { className: "ak-btn", onClick: () => setDraft([...draft, emptyType(draft.length)]), children: "Aggiungi tipologia" })] });
98
+ }
99
+ function AvailabilitySettings({ store }) {
100
+ const [weekly, setWeekly] = useState(store.weekly);
101
+ const [overrides, setOverrides] = useState(store.overrides);
102
+ useEffect(() => { setWeekly(store.weekly); setOverrides(store.overrides); }, [store.weekly, store.overrides]);
103
+ const addWindow = (weekday) => setWeekly([...weekly, { weekday, startMinute: 9 * 60, endMinute: 13 * 60 }]);
104
+ return _jsxs("section", { className: "bk-settings-section", children: [_jsxs("header", { children: [_jsx("p", { className: "bk-kicker", children: "Disponibilit\u00E0 positiva" }), _jsx("h2", { children: "Quando si pu\u00F2 prenotare." })] }), _jsx("div", { className: "bk-week", children: days.map((label, day) => {
105
+ const weekday = day + 1;
106
+ return _jsxs("div", { children: [_jsx("strong", { children: label }), _jsxs("div", { children: [weekly.filter((window) => window.weekday === weekday).map((window, index) => {
107
+ const actual = weekly.indexOf(window);
108
+ return _jsxs("span", { children: [_jsx("input", { type: "time", value: time(window.startMinute), onChange: (e) => setWeekly(weekly.map((value, i) => i === actual ? { ...value, startMinute: minute(e.target.value) } : value)) }), _jsx("span", { children: "\u2014" }), _jsx("input", { type: "time", value: time(window.endMinute), onChange: (e) => setWeekly(weekly.map((value, i) => i === actual ? { ...value, endMinute: minute(e.target.value) } : value)) }), _jsx("button", { "aria-label": "Rimuovi fascia", onClick: () => setWeekly(weekly.filter((_, i) => i !== actual)), children: "\u00D7" })] }, `${window.startMinute}-${index}`);
109
+ }), _jsx("button", { onClick: () => addWindow(weekday), children: "+ fascia" })] })] }, weekday);
110
+ }) }), _jsx("h3", { children: "Eccezioni per data" }), _jsx("div", { className: "bk-overrides", children: overrides.map((override, index) => _jsxs("div", { children: [_jsx("input", { type: "date", value: override.date, onChange: (e) => setOverrides(overrides.map((v, i) => i === index ? { ...v, date: e.target.value } : v)) }), _jsxs("select", { value: override.kind, onChange: (e) => setOverrides(overrides.map((v, i) => i === index ? { ...v, kind: e.target.value, windows: e.target.value === "closed" ? [] : (v.windows.length ? v.windows : [{ startMinute: 9 * 60, endMinute: 13 * 60 }]) } : v)), children: [_jsx("option", { value: "closed", children: "Non disponibile" }), _jsx("option", { value: "custom", children: "Orari speciali" })] }), override.kind === "custom" && _jsxs(_Fragment, { children: [_jsx("input", { type: "time", value: time(override.windows[0]?.startMinute ?? 540), onChange: (e) => setOverrides(overrides.map((v, i) => i === index ? { ...v, windows: [{ startMinute: minute(e.target.value), endMinute: v.windows[0]?.endMinute ?? 780 }] } : v)) }), _jsx("input", { type: "time", value: time(override.windows[0]?.endMinute ?? 780), onChange: (e) => setOverrides(overrides.map((v, i) => i === index ? { ...v, windows: [{ startMinute: v.windows[0]?.startMinute ?? 540, endMinute: minute(e.target.value) }] } : v)) })] }), _jsx("input", { placeholder: "Nota", value: override.note || "", onChange: (e) => setOverrides(overrides.map((v, i) => i === index ? { ...v, note: e.target.value } : v)) }), _jsx("button", { onClick: () => setOverrides(overrides.filter((_, i) => i !== index)), children: "Rimuovi" })] }, override.id || index)) }), _jsxs("div", { className: "bk-actions", children: [_jsx("button", { className: "ak-btn", onClick: () => setOverrides([...overrides, { date: "", kind: "closed", windows: [] }]), children: "Aggiungi eccezione" }), _jsx("button", { className: "ak-btn ak-btn-primary", disabled: store.busy, onClick: () => void store.saveAvailability(weekly, overrides), children: "Salva disponibilit\u00E0" })] })] });
111
+ }
112
+ function GoogleSettings({ store }) {
113
+ const [busy, setBusy] = useState(store.google.calendars.filter((calendar) => calendar.busy).map((calendar) => calendar.id));
114
+ const [target, setTarget] = useState(store.google.calendars.find((calendar) => calendar.booking)?.id || "");
115
+ useEffect(() => {
116
+ setBusy(store.google.calendars.filter((calendar) => calendar.busy).map((calendar) => calendar.id));
117
+ setTarget(store.google.calendars.find((calendar) => calendar.booking)?.id || "");
118
+ }, [store.google]);
119
+ if (!store.google.connected)
120
+ return _jsxs("section", { className: "bk-settings-section", children: [_jsxs("header", { children: [_jsx("p", { className: "bk-kicker", children: "Sincronizzazione" }), _jsx("h2", { children: "Collega Google Calendar." })] }), _jsx("p", { children: "Leggeremo gli impegni dai calendari scelti e creeremo gli eventi, con Google Meet quando necessario, solo dopo l\u2019approvazione." }), _jsx("a", { className: "ak-btn ak-btn-primary", href: "/admin/api/booking/google/connect", children: "Collega account Google" })] });
121
+ return _jsxs("section", { className: "bk-settings-section", children: [_jsxs("header", { children: [_jsx("p", { className: "bk-kicker", children: "Account collegato" }), _jsx("h2", { children: store.google.connection?.account_email })] }), _jsxs("p", { children: ["Stato token: ", store.google.connection?.token_status] }), _jsx("h3", { children: "Calendari che rendono occupato" }), _jsx("div", { className: "bk-google-list", children: store.google.calendars.map((calendar) => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: busy.includes(calendar.id), onChange: (e) => setBusy(e.target.checked ? [...busy, calendar.id] : busy.filter((id) => id !== calendar.id)) }), _jsxs("span", { children: [calendar.summary, calendar.primary ? " · principale" : ""] })] }, calendar.id)) }), _jsxs("label", { className: "bk-target", children: ["Calendario in cui creare gli appuntamenti", _jsxs("select", { value: target, onChange: (e) => setTarget(e.target.value), children: [_jsx("option", { value: "", children: "Scegli\u2026" }), store.google.calendars.filter((calendar) => calendar.accessRole === "owner").map((calendar) => _jsx("option", { value: calendar.id, children: calendar.summary }, calendar.id))] })] }), _jsxs("div", { className: "bk-actions", children: [_jsx("button", { className: "ak-btn ak-btn-primary", disabled: !target || !busy.length || store.busy, onClick: () => void store.saveGoogle(busy, target), children: "Salva calendari" }), _jsx("button", { className: "ak-btn", onClick: () => void store.refreshGoogle(), children: "Aggiorna elenco" }), _jsx("button", { className: "ak-btn is-danger", onClick: () => void store.disconnectGoogle(), children: "Scollega" })] })] });
122
+ }
123
+ function ActivationSettings({ store }) {
124
+ const labels = {
125
+ contact: "Email proprietario", address: "Indirizzo per incontri in presenza",
126
+ turnstile: "Turnstile configurato", rateLimit: "Sale anti-abuso",
127
+ email: "Invio email configurato", googleOAuth: "Credenziali OAuth Google",
128
+ type: "Almeno una tipologia",
129
+ availability: "Disponibilità ricorrente", google: "Account Google",
130
+ targetCalendar: "Calendario di destinazione", meet: "Google Meet disponibile",
131
+ };
132
+ const state = store.activation;
133
+ return _jsxs("section", { className: "bk-settings-section", children: [_jsxs("header", { children: [_jsx("p", { className: "bk-kicker", children: "Pubblicazione" }), _jsx("h2", { children: state?.enabled ? "Prenotazioni attive." : "Prenotazioni non pubbliche." })] }), _jsxs("p", { children: ["Quando la funzione \u00E8 spenta, ", _jsx("code", { children: "/prenota/" }), ", le API pubbliche e le voci di menu rispondono come se non esistessero."] }), _jsx("div", { className: "bk-checklist", children: Object.entries(state?.checks || {}).map(([id, ready]) => _jsxs("div", { className: ready ? "is-ready" : "", children: [_jsx("span", { children: ready ? "✓" : "—" }), labels[id] || id] }, id)) }), _jsx("button", { className: "ak-btn ak-btn-primary", disabled: store.busy || (!state?.enabled && !state?.ready), onClick: () => void store.setEnabled(!state?.enabled), children: state?.enabled ? "Disattiva prenotazioni" : "Attiva e pubblica" })] });
134
+ }
@@ -0,0 +1,37 @@
1
+ import type { Delivery, DeliverySettings as DeliverySettingsModel, DeliverySummary } from "../lib/deliveries";
2
+ type DeliveryAction = <T>(payload: Record<string, unknown>, options?: {
3
+ reload?: boolean;
4
+ }) => Promise<T>;
5
+ export declare function NewDeliveryButton({ onCreate, disabled }: {
6
+ onCreate: () => void;
7
+ disabled?: boolean;
8
+ }): import("react").JSX.Element;
9
+ export declare function DeliveriesList({ rows, current, disabled, onOpen }: {
10
+ rows: DeliverySummary[];
11
+ current?: string;
12
+ disabled?: boolean;
13
+ onOpen: (id: string) => void;
14
+ }): import("react").JSX.Element;
15
+ export declare function DeliveriesTotal({ rows }: {
16
+ rows: DeliverySummary[];
17
+ }): import("react").JSX.Element | null;
18
+ interface WorkspaceProps {
19
+ delivery: Delivery | null;
20
+ busy: boolean;
21
+ onAction: DeliveryAction;
22
+ onRefresh: () => Promise<void>;
23
+ onReload: () => Promise<void>;
24
+ onRead: (id: string) => Promise<Delivery | null>;
25
+ onDeleted: () => void;
26
+ onOpen: (id: string) => void;
27
+ onSelected: (id: string) => void;
28
+ }
29
+ export declare function DeliveryWorkspace({ delivery: fromServer, busy, onAction, onRefresh, onReload, onRead, onDeleted, onOpen, onSelected, }: WorkspaceProps): import("react").JSX.Element;
30
+ export declare function DeliverySettings({ settings, onAction, pickMedia }: {
31
+ settings: DeliverySettingsModel;
32
+ onAction: DeliveryAction;
33
+ pickMedia: (options: {
34
+ multiple: boolean;
35
+ }) => Promise<string[] | null>;
36
+ }): import("react").JSX.Element;
37
+ export {};