@compill/admin 1.0.105 → 1.0.107

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/index.mjs ADDED
@@ -0,0 +1,2000 @@
1
+ import { jsxs, jsx, Fragment } from '@soperio/jsx-runtime';
2
+ import { useApiQuery, useInvalidateQuery, useApiMutation, useMutate, useInvalidateMutation, useInvalidateParentMutation, useApiQueries } from '@compill/api';
3
+ import { QueryLoadingState, RetryOnError, Shimmer, FlexCenter, ModalLoadingOverlay, TabContainer } from '@compill/components';
4
+ import { mdiCircleSmall, mdiDatabaseRefreshOutline, mdiPost, mdiEye, mdiEyeOff, mdiCloudUpload, mdiOpenInNew, mdiArrowUpBoldBox, mdiArrowDownBoldBox, mdiPlusThick, mdiFilter, mdiArrowLeft, mdiPencil, mdiDelete, mdiPublish, mdiPublishOff, mdiCog, mdiDotsVertical, mdiLogout, mdiRefresh } from '@mdi/js';
5
+ import { Button, Icon, Container, Tile, Popover, Modal, IconButton, Checkbox, Badge, Avatar, Collapse, Popup } from '@valerya/ui';
6
+ import { isArray } from 'es-toolkit/compat';
7
+ import Link from 'next/link';
8
+ import { useParams, useNavigate, useLocation, useMatch, Link as Link$1, Outlet } from 'react-router-dom';
9
+ import React8 from 'react';
10
+ import { INVALIDATE_API, useInvalidatePage, API } from '@compill/admin-api';
11
+ import { useFormikContext, Formik, Form } from 'formik';
12
+ import { toast } from 'react-toastify';
13
+ import { useHotkeys } from 'react-hotkeys-hook';
14
+ import { FormRenderer, FormProvider, SubmitButton, TextArea, mergeInitialFormValues, FieldLabel } from '@compill/form';
15
+ import { createContext, isFunction, runIfFn } from '@soperio/react';
16
+ import { FormEditor } from '@compill/form-editor';
17
+ import { ImageExtension } from '@compill/editor';
18
+ import { createPortal } from 'react-dom';
19
+ import { useRouter } from 'next/router';
20
+ import { TableContextProvider, useTableContext, Table } from '@compill/table';
21
+ import { sortBy, capitalize, isEqual } from 'es-toolkit';
22
+ import { useBoolean, useOpenLink } from '@compill/hooks';
23
+ import { AppEnv } from '@compill/env';
24
+ import { createColumnHelper } from '@tanstack/react-table';
25
+ import { useQueryClient } from '@tanstack/react-query';
26
+ import Image from 'next/image';
27
+ import { useSessionUser, useSessionLogout } from '@compill/auth';
28
+
29
+ // src/lib/SectionTitle.tsx
30
+ function SectionTitle({ children, ...props }) {
31
+ return /* @__PURE__ */ jsx("h2", { textSize: "xl", fontWeight: "600", textColor: "slate-800", ...props, children });
32
+ }
33
+ function Breadcrumbs({ breadcrumbs, ...props }) {
34
+ if (isArray(breadcrumbs)) {
35
+ return /* @__PURE__ */ jsx("div", { dflex: true, alignItems: "center", trait: "typo.h5", ...props, children: breadcrumbs.map((b, index) => /* @__PURE__ */ jsx(BreadcrumbItem, { breadcrumb: b, showSeparator: index > 0 }, index)) });
36
+ }
37
+ return /* @__PURE__ */ jsx(QueryBreadcrumbs, { queryFn: breadcrumbs.queryFn, queryField: breadcrumbs.queryField, generate: breadcrumbs.generate });
38
+ }
39
+ function QueryBreadcrumbs({ queryFn, queryField, generate, ...props }) {
40
+ const params = useParams();
41
+ const id = queryField ? params[queryField] : void 0;
42
+ const { data, isLoading, isError } = useApiQuery([""], queryFn, id);
43
+ if (isLoading || isError) {
44
+ return /* @__PURE__ */ jsxs("div", { dflex: true, alignItems: "center", children: [
45
+ /* @__PURE__ */ jsx(Shimmer, { h: "8", w: "24" }),
46
+ /* @__PURE__ */ jsx(Icon, { path: mdiCircleSmall, mx: "1" }),
47
+ /* @__PURE__ */ jsx(Shimmer, { h: "8", w: "20" })
48
+ ] });
49
+ }
50
+ const breadcrumbs = generate(data);
51
+ return /* @__PURE__ */ jsx("div", { dflex: true, alignItems: "center", trait: "typo.h5", ...props, children: breadcrumbs.map((b, index) => /* @__PURE__ */ jsx(BreadcrumbItem, { breadcrumb: b, showSeparator: index > 0 }, index)) });
52
+ }
53
+ function BreadcrumbItem({ breadcrumb, showSeparator }) {
54
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
55
+ showSeparator && /* @__PURE__ */ jsx(Icon, { path: mdiCircleSmall, mx: "1" }),
56
+ /* @__PURE__ */ jsx(Link, { href: `/${breadcrumb.path || "#"}`, children: /* @__PURE__ */ jsx(
57
+ "span",
58
+ {
59
+ hover_textDecoration: breadcrumb.path ? "underline" : void 0,
60
+ cursor: breadcrumb.path ? "pointer" : void 0,
61
+ children: breadcrumb.label
62
+ }
63
+ ) })
64
+ ] });
65
+ }
66
+ function DialogButton({ buildDialog, ...props }) {
67
+ const [showDialog, setShowDialog] = React8.useState(false);
68
+ const onShowDialog = React8.useCallback((event) => {
69
+ event?.preventDefault();
70
+ event?.stopPropagation();
71
+ setShowDialog(true);
72
+ }, [setShowDialog]);
73
+ const onCloseDialog = React8.useCallback(() => {
74
+ setShowDialog(false);
75
+ }, [setShowDialog]);
76
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
77
+ /* @__PURE__ */ jsx(Button, { onClick: onShowDialog, ...props }),
78
+ showDialog && buildDialog(onCloseDialog)
79
+ ] });
80
+ }
81
+ function ButtonBar({ children, ...props }) {
82
+ return /* @__PURE__ */ jsx("div", { dflex: true, border: "1px", borderColor: "zinc-200", divideX: "1px", divideColor: "zinc-200", rounded: "lg", overflow: "hidden", ...props, children });
83
+ }
84
+ var ButtonBarButton = React8.forwardRef(
85
+ function({ icon, children, ...props }, ref) {
86
+ return /* @__PURE__ */ jsxs(
87
+ Button,
88
+ {
89
+ scheme: "dark",
90
+ size: "sm",
91
+ aspectRatio: icon && !children ? "square" : void 0,
92
+ variant: "borderless",
93
+ dflex: true,
94
+ alignItems: "center",
95
+ placeContent: "center",
96
+ corners: "square",
97
+ gap: "2",
98
+ ...props,
99
+ ref,
100
+ children: [
101
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon }),
102
+ children
103
+ ]
104
+ }
105
+ );
106
+ }
107
+ );
108
+ var ButtonBarSubmitButton = React8.forwardRef(
109
+ function({ useDirty, disabled, icon, children, ...props }, ref) {
110
+ const { dirty, handleSubmit } = useFormikContext() ?? { dirty: false, handleSubmit: void 0};
111
+ const onSubmit = React8.useCallback(() => handleSubmit(), [handleSubmit]);
112
+ return /* @__PURE__ */ jsxs(
113
+ Button,
114
+ {
115
+ scheme: "dark",
116
+ size: "sm",
117
+ aspectRatio: icon && !children ? "square" : void 0,
118
+ variant: "borderless",
119
+ dflex: true,
120
+ alignItems: "center",
121
+ placeContent: "center",
122
+ corners: "square",
123
+ gap: "2",
124
+ disabled: useDirty && !dirty || disabled,
125
+ onClick: onSubmit,
126
+ ...props,
127
+ ref,
128
+ children: [
129
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon }),
130
+ children
131
+ ]
132
+ }
133
+ );
134
+ }
135
+ );
136
+ function ButtonBarDialogButton({ icon, children, ...props }) {
137
+ return /* @__PURE__ */ jsxs(
138
+ DialogButton,
139
+ {
140
+ scheme: "dark",
141
+ size: "sm",
142
+ aspectRatio: icon && !children ? "square" : void 0,
143
+ variant: "borderless",
144
+ dflex: true,
145
+ alignItems: "center",
146
+ placeContent: "center",
147
+ corners: "square",
148
+ gap: "2",
149
+ ...props,
150
+ children: [
151
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon }),
152
+ children
153
+ ]
154
+ }
155
+ );
156
+ }
157
+ function InvalidateButton({ pathOrPermalink, ...props }) {
158
+ const api = INVALIDATE_API.new(pathOrPermalink);
159
+ const mutation = useApiMutation(api.invalidate, api.queryKey);
160
+ const invalidate = useMutate(mutation, { successMsg: "Page successfully invalidated" });
161
+ return /* @__PURE__ */ jsx(
162
+ ButtonBarButton,
163
+ {
164
+ title: "Regenerate",
165
+ disabled: mutation.isLoading,
166
+ onClick: invalidate,
167
+ icon: mdiDatabaseRefreshOutline,
168
+ ...props
169
+ }
170
+ );
171
+ }
172
+ function NavigateButton({ path, ...props }) {
173
+ const navigate = useNavigate();
174
+ const handleClick = React8.useCallback(() => {
175
+ navigate(path);
176
+ }, [navigate, path]);
177
+ return /* @__PURE__ */ jsx(Button, { type: "submit", scheme: "dark", variant: "glass", corners: "pill", w: "10", h: "10", onClick: handleClick, ...props, children: /* @__PURE__ */ jsx(Icon, { path: mdiPost }) });
178
+ }
179
+ function PublishButton({
180
+ status,
181
+ queryId,
182
+ api,
183
+ ...props
184
+ }) {
185
+ const isDraft = status == "draft";
186
+ const mutation = useInvalidateMutation(isDraft ? api.publish : api.unpublish, api.queryKey, queryId, { networkMode: "always" });
187
+ const publish = React8.useCallback(() => {
188
+ mutation.reset();
189
+ mutation.mutateAsync(queryId).then(() => toast.success(isDraft ? "Published!" : "Unpublished!")).catch((error) => toast.error(`Error: ${error}`));
190
+ }, [mutation, queryId]);
191
+ return /* @__PURE__ */ jsx(
192
+ ButtonBarButton,
193
+ {
194
+ disabled: mutation.isLoading,
195
+ onClick: publish,
196
+ icon: isDraft ? mdiEye : mdiEyeOff,
197
+ ...props,
198
+ children: isDraft ? "Publish" : "Switch to draft"
199
+ }
200
+ );
201
+ }
202
+ function UpdateButton({ ...props }) {
203
+ const { dirty, handleSubmit } = useFormikContext() ?? { dirty: false, handleSubmit: void 0};
204
+ useHotkeys("ctrl+s", () => {
205
+ if (dirty && !props.disabled) handleSubmit();
206
+ }, { preventDefault: true }, [dirty, props, handleSubmit]);
207
+ return /* @__PURE__ */ jsx(ButtonBarSubmitButton, { icon: mdiCloudUpload, ...props, children: "Update" });
208
+ }
209
+ function ViewButton({ label, path, icon, ...props }) {
210
+ const openPage = React8.useCallback(() => {
211
+ window.open(path, "_blank");
212
+ }, [path]);
213
+ return /* @__PURE__ */ jsx(
214
+ ButtonBarButton,
215
+ {
216
+ onClick: openPage,
217
+ dflex: true,
218
+ alignItems: "center",
219
+ gap: "2",
220
+ icon: icon || mdiOpenInNew,
221
+ ...props,
222
+ children: label
223
+ }
224
+ );
225
+ }
226
+ function OrderCell({ api, postId, order }) {
227
+ const mutationDown = useInvalidateMutation(api.moveDown, api.queryKey);
228
+ const mutationUp = useInvalidateMutation(api.moveUp, api.queryKey);
229
+ const moveDown = React8.useCallback((e) => {
230
+ e.stopPropagation();
231
+ mutationDown.mutateAsync(postId).catch((error) => {
232
+ toast.error(`Error: ${error}`);
233
+ });
234
+ }, [mutationDown, postId]);
235
+ const moveUp = React8.useCallback((e) => {
236
+ e.stopPropagation();
237
+ mutationUp.mutateAsync(postId).catch((error) => {
238
+ toast.error(`Error: ${error}`);
239
+ });
240
+ }, [mutationUp, postId]);
241
+ return /* @__PURE__ */ jsxs(FlexCenter, { placeContent: "start", numericFontVariant: "tabular-nums", children: [
242
+ order,
243
+ /* @__PURE__ */ jsx(Button, { ms: "3", variant: "borderless", scheme: "dark", onClick: moveUp, children: /* @__PURE__ */ jsx(Icon, { path: mdiArrowUpBoldBox }) }),
244
+ /* @__PURE__ */ jsx(Button, { variant: "borderless", scheme: "dark", onClick: moveDown, children: /* @__PURE__ */ jsx(Icon, { path: mdiArrowDownBoldBox }) })
245
+ ] });
246
+ }
247
+ function PageContainer({ children, ...props }) {
248
+ return /* @__PURE__ */ jsx(Container, { center: true, dflex: true, flexCol: true, gap: "8", ...props, children });
249
+ }
250
+ function PageMain({ children, ...props }) {
251
+ return /* @__PURE__ */ jsx(Tile, { scheme: "light", p: "5", ...props, children });
252
+ }
253
+ function PageContentEditor({ name, ...props }) {
254
+ const extensions = [ImageExtension];
255
+ return /* @__PURE__ */ jsx(PageMain, { h: "min", ...props, children: /* @__PURE__ */ jsx(
256
+ FormEditor,
257
+ {
258
+ minH: "128",
259
+ minW: "144",
260
+ maxW: props.maxW,
261
+ name,
262
+ placeHolder: "Write here...",
263
+ extensions
264
+ }
265
+ ) });
266
+ }
267
+ function PageQueryStateContainerInner({ queryId, api, apiFn, loadingStyles, errorStyles, children, ...props }) {
268
+ const { data, isLoading, isError } = apiFn == "getAll" ? useApiQuery(api.queryKey, api.getAll, props.apiParams) : useApiQuery(api.queryKey, api.get, queryId);
269
+ const invalidate = useInvalidateQuery(api.queryKey, queryId);
270
+ if (isLoading)
271
+ return /* @__PURE__ */ jsx(QueryLoadingState, { w: "full", h: "100%", ...loadingStyles });
272
+ if (isError)
273
+ return /* @__PURE__ */ jsx(RetryOnError, { p: "0", onClick: invalidate, ...errorStyles });
274
+ return /* @__PURE__ */ jsx(PageContainer, { ...props, children: apiFn == "get" ? children(data) : children(data) });
275
+ }
276
+ var PageQueryStateContainer = React8.forwardRef(PageQueryStateContainerInner);
277
+ function PageSidebar({ children, ...props }) {
278
+ return /* @__PURE__ */ jsx("div", { w: "112", minW: "112", minH: "100%", gap: "8", dflex: true, flexCol: true, ...props, children });
279
+ }
280
+ function PageSectionTitle({ children, ...props }) {
281
+ return /* @__PURE__ */ jsx("div", { trait: "typo.h6", mb: "5", ...props, children });
282
+ }
283
+ function PageSidebarSection({ title, children, ...props }) {
284
+ return /* @__PURE__ */ jsxs("div", { w: "full", ...props, children: [
285
+ title && /* @__PURE__ */ jsx(PageSectionTitle, { children: title }),
286
+ children
287
+ ] });
288
+ }
289
+ function PageTitle({ children, ...props }) {
290
+ return /* @__PURE__ */ jsx("div", { trait: "typo.h5", ...props, children });
291
+ }
292
+ function PageTopBar({ title, breadcrumbs, children, ...props }) {
293
+ return /* @__PURE__ */ jsxs(FlexCenter, { gap: "3", minH: "9", ...props, children: [
294
+ title && /* @__PURE__ */ jsx(PageTitle, { children: title }),
295
+ breadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs, { breadcrumbs }),
296
+ /* @__PURE__ */ jsx("div", { flexGrow: true }),
297
+ children
298
+ ] });
299
+ }
300
+ var [CP, usePageTabbedTopBarContext] = createContext();
301
+ function PageTabbedTopBarProvider({ children }) {
302
+ const [containerEl, setContainerEl] = React8.useState(null);
303
+ React8.createRef();
304
+ return /* @__PURE__ */ jsx(CP, { value: { containerEl, setContainerEl }, children });
305
+ }
306
+ function PageTabbedTopBar({ title, breadcrumbs, children, ...props }) {
307
+ const ref = React8.createRef();
308
+ const { setContainerEl } = usePageTabbedTopBarContext();
309
+ const [isSet, setIsSet] = React8.useState(false);
310
+ React8.useEffect(() => {
311
+ if (!isSet && ref.current) {
312
+ setContainerEl(ref.current);
313
+ setIsSet(true);
314
+ }
315
+ return () => setContainerEl(null);
316
+ }, []);
317
+ return /* @__PURE__ */ jsx(FlexCenter, { gap: "3", minH: "9", ...props, children: /* @__PURE__ */ jsxs(Fragment, { children: [
318
+ title && /* @__PURE__ */ jsx(PageTitle, { children: title }),
319
+ breadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs, { breadcrumbs }),
320
+ /* @__PURE__ */ jsx("div", { flexGrow: true }),
321
+ /* @__PURE__ */ jsx("div", { ref, dflex: true, flexRow: true, gap: "3" }),
322
+ children
323
+ ] }) });
324
+ }
325
+ function PageTopBarToolbar({ trackingRef, children }) {
326
+ const { containerEl } = usePageTabbedTopBarContext();
327
+ const [visible, setVisible] = React8.useState(false);
328
+ const portal = React8.useMemo(() => {
329
+ const node = containerEl;
330
+ return node;
331
+ }, [containerEl]);
332
+ const host = containerEl ?? (typeof window !== "undefined" ? document.body : void 0);
333
+ React8.useLayoutEffect(() => {
334
+ if (!portal || !host)
335
+ return;
336
+ try {
337
+ if (visible)
338
+ host.appendChild(portal);
339
+ else
340
+ host.removeChild(portal);
341
+ } catch (e) {
342
+ }
343
+ return () => {
344
+ try {
345
+ host.removeChild(portal);
346
+ } catch (e) {
347
+ }
348
+ };
349
+ }, [visible, portal, host]);
350
+ const callback = React8.useCallback((entries) => {
351
+ setVisible(entries[0].isVisible);
352
+ }, [children]);
353
+ React8.useEffect(() => {
354
+ const opts = {
355
+ root: null,
356
+ rootMargin: "0px",
357
+ threshold: 0,
358
+ /* required options*/
359
+ trackVisibility: true,
360
+ delay: 100
361
+ };
362
+ const observerScroll = new IntersectionObserver(callback, opts);
363
+ if (trackingRef)
364
+ observerScroll.observe(trackingRef);
365
+ return () => observerScroll.disconnect();
366
+ }, [trackingRef, callback, children]);
367
+ if (host && portal)
368
+ return createPortal(children, portal);
369
+ return null;
370
+ }
371
+ function ScreenTopBar({ tabbed, breadcrumbs, api, item, isLoading, buttonBar, trackingRef }) {
372
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
373
+ tabbed && /* @__PURE__ */ jsx(PageTopBarToolbar, { trackingRef, children: /* @__PURE__ */ jsx(Buttons, { api, item, isLoading, buttonBar }) }),
374
+ !tabbed && /* @__PURE__ */ jsx(PageTopBar, { breadcrumbs, children: /* @__PURE__ */ jsx(Buttons, { api, item, isLoading, buttonBar }) })
375
+ ] });
376
+ }
377
+ function Buttons({ api, item, isLoading, buttonBar }) {
378
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
379
+ buttonBar && buttonBar.length > 0 && /* @__PURE__ */ jsx(ButtonBar, { children: buttonBar.map((button, index) => /* @__PURE__ */ jsxs(React8.Fragment, { children: [
380
+ button.type === "link" && /* @__PURE__ */ jsx(ViewButton, { label: button.label, path: button.path, icon: button.icon }),
381
+ button.type === "invalidate" && /* @__PURE__ */ jsx(InvalidateButton, { pathOrPermalink: button.pathOrPermalink }),
382
+ button.type == "custom" && button.render(item)
383
+ ] }, index)) }),
384
+ /* @__PURE__ */ jsxs(ButtonBar, { children: [
385
+ api instanceof API && /* @__PURE__ */ jsx(PublishButton, { api, queryId: item.id, status: item.status, disabled: isLoading }),
386
+ /* @__PURE__ */ jsx(UpdateButton, { disabled: isLoading })
387
+ ] })
388
+ ] });
389
+ }
390
+ function useQueryField(queryField, useNextRouter) {
391
+ if (useNextRouter) {
392
+ const router = useRouter();
393
+ return router.query[queryField];
394
+ }
395
+ const { [queryField]: id } = useParams();
396
+ return id;
397
+ }
398
+ function DetailsView({ queryField, api, useNextRouter, processInput, screen, tabbed, ...props }) {
399
+ const id = useQueryField(queryField, useNextRouter);
400
+ const ref = React8.useRef(null);
401
+ return /* @__PURE__ */ jsx(PageQueryStateContainer, { api, apiFn: "get", queryId: id, ref, p: "5", ...props, children: (item) => /* @__PURE__ */ jsx(Internal, { item, screen, api, tabbed, processInput, containerRef: ref }) });
402
+ }
403
+ function Internal({ item, screen, api, processInput, tabbed, containerRef }) {
404
+ const pScreen = runIfFn(screen, item);
405
+ const { breadcrumbs, schema, initialValues, header, sections, buttonBar, type, invalidateParentQueryKey, invalidatePage } = pScreen;
406
+ const mutation = useInvalidateParentMutation(api.update, invalidateParentQueryKey ?? api.queryKey, { networkMode: "always" });
407
+ const invalidatePageFn = useInvalidatePage(invalidatePage || "");
408
+ const save = useMutate(mutation, {
409
+ processInput,
410
+ successMsg: (item2, values) => `${item2.title || item2.name} updated.`,
411
+ errorMsg: (error, item2) => `Error updating ${item2.title || item2.name}: ${error}`,
412
+ onSuccess: () => {
413
+ if (invalidatePage)
414
+ invalidatePageFn();
415
+ }
416
+ });
417
+ let editorMaxW = void 0;
418
+ if (!type || type == "post")
419
+ editorMaxW = "calc(1280px - 28rem)";
420
+ else if (type == "section")
421
+ editorMaxW = "calc(1280px - 22rem)";
422
+ return /* @__PURE__ */ jsxs(
423
+ FormProvider,
424
+ {
425
+ initialValues: runIfFn(initialValues, item),
426
+ validationSchema: schema,
427
+ onSubmit: save,
428
+ enableReinitialize: true,
429
+ children: [
430
+ /* @__PURE__ */ jsx(
431
+ ScreenTopBar,
432
+ {
433
+ tabbed,
434
+ api,
435
+ breadcrumbs,
436
+ buttonBar,
437
+ item,
438
+ isLoading: mutation.isLoading,
439
+ trackingRef: containerRef?.current
440
+ }
441
+ ),
442
+ type == "form" && sections?.map((section, index) => /* @__PURE__ */ jsx(Section, { section, item }, index)),
443
+ (pScreen.type == "post" || pScreen.type == "section") && /* @__PURE__ */ jsx("div", { p: "5", bgColor: "slate-100", rounded: "lg", children: /* @__PURE__ */ jsxs(PageContainer, { w: editorMaxW ? "auto" : "full", size: "x2", id: "pagecontainer", children: [
444
+ header,
445
+ /* @__PURE__ */ jsxs("div", { dflex: true, gap: "5", children: [
446
+ /* @__PURE__ */ jsxs("div", { dflex: true, flexCol: true, gap: "8", flexGrow: true, children: [
447
+ pScreen.editor?.type != "textarea" && // TODO Find a way to make this editor shrink in width when resizing the window...
448
+ /* @__PURE__ */ jsx(PageContentEditor, { name: "content", maxW: editorMaxW, minW: "144", shadow: true, rounded: "lg" }),
449
+ pScreen.editor?.type == "textarea" && /* @__PURE__ */ jsx(
450
+ TextArea,
451
+ {
452
+ name: pScreen.editor?.name,
453
+ maxW: editorMaxW,
454
+ minW: editorMaxW,
455
+ w: editorMaxW,
456
+ minH: "128",
457
+ rows: 25,
458
+ bgColor: "white",
459
+ border: "0",
460
+ shadow: true,
461
+ p: "5",
462
+ textColor: "slate-800"
463
+ }
464
+ ),
465
+ pScreen.editorFooter
466
+ ] }),
467
+ /* @__PURE__ */ jsx(PageSidebar, { children: sections?.map((section, index) => /* @__PURE__ */ jsx(Section, { section, item, cardStyle: true }, index)) })
468
+ ] })
469
+ ] }) })
470
+ ]
471
+ }
472
+ );
473
+ }
474
+ function Section({ section, item, cardStyle }) {
475
+ if (section.type === "section") {
476
+ const style = cardStyle ? {
477
+ bgColor: "white",
478
+ rounded: "lg",
479
+ p: "5",
480
+ textSize: "sm",
481
+ shadow: true
482
+ } : {};
483
+ return /* @__PURE__ */ jsx(PageSidebarSection, { title: section.title, ...style, children: /* @__PURE__ */ jsx(FormRenderer, { form: runIfFn(section.form, item) }) });
484
+ }
485
+ if (section.type === "custom")
486
+ return runIfFn(section.component, item);
487
+ return null;
488
+ }
489
+ function useApi(api, queryField) {
490
+ const params = useParams();
491
+ if (queryField === void 0)
492
+ return { id: void 0, api };
493
+ const { [queryField]: id } = params;
494
+ return { id, api: runIfFn(api, id) };
495
+ }
496
+ function QueryWrapper({
497
+ api,
498
+ queryField,
499
+ fn,
500
+ transformFn,
501
+ config,
502
+ tabbed,
503
+ ...props
504
+ }) {
505
+ const { id, api: mApi } = useApi(api, queryField);
506
+ const { data } = useApiQuery(mApi.queryKey, fn === "get" || fn === "getTransformed" ? mApi.get : mApi.getAll, isFunction(api) ? void 0 : id);
507
+ const transformedData = React8.useMemo(() => {
508
+ if (data && (fn === "getTransformed" || fn === "getAllTransformed")) {
509
+ if (!transformFn)
510
+ console.warn(`QueryWrapperDialog: you forgot to pass transformFn as parameter for fn ${fn}`);
511
+ return transformFn?.(data);
512
+ }
513
+ return data;
514
+ }, [data]);
515
+ if (!data)
516
+ return null;
517
+ return /* @__PURE__ */ jsx(ScreenRenderer, { config: config(transformedData), tabbed, ...props });
518
+ }
519
+ function TabbedView({ queryField, api, screen, ...props }) {
520
+ const { [queryField]: id } = useParams();
521
+ return /* @__PURE__ */ jsx(
522
+ PageQueryStateContainer,
523
+ {
524
+ queryId: id,
525
+ api,
526
+ apiFn: "get",
527
+ p: "5",
528
+ children: (city) => {
529
+ const { breadcrumbs, tabs } = runIfFn(screen, city);
530
+ return /* @__PURE__ */ jsxs(PageTabbedTopBarProvider, { children: [
531
+ /* @__PURE__ */ jsx(PageTabbedTopBar, { breadcrumbs, mb: "-3" }),
532
+ /* @__PURE__ */ jsx(
533
+ TabContainer,
534
+ {
535
+ tabs: tabs.map((tab) => tab.label),
536
+ saveKey: `tab-${id}`,
537
+ mt: "-3",
538
+ mb: "3",
539
+ id: "fff",
540
+ children: tabs.map((tab, index) => /* @__PURE__ */ jsx(TabView, { tab }, index))
541
+ }
542
+ )
543
+ ] });
544
+ }
545
+ }
546
+ );
547
+ }
548
+ function TabView({ tab }) {
549
+ if (!tab.component && !tab.config)
550
+ throw new Error(`Screen config for tabbed view: one of your tabs does not have a component or config declared: ${tab.label}`);
551
+ if (tab.component)
552
+ return tab.component();
553
+ return /* @__PURE__ */ jsx(ScreenRenderer, { config: tab.config, tabbed: true, p: "0" });
554
+ }
555
+ var [provider, useContext] = createContext();
556
+ function getColId(column) {
557
+ return column.accessorKey ?? column.id;
558
+ }
559
+ function TableContainerContextProvider({ initialVisibleColumns, columns, children }) {
560
+ const [showFilters, setShowFilters] = React8.useState(false);
561
+ const [showMassActions, setShowMassActions] = React8.useState(false);
562
+ const [visibleColumnIds, setVisibleColumnIds] = React8.useState(initialVisibleColumns ?? columns?.map((col) => getColId(col)) ?? []);
563
+ const filteredColumns = columns?.filter((col) => visibleColumnIds.includes(getColId(col))) ?? [];
564
+ const toggleColumnVisibility = React8.useCallback((id) => {
565
+ const index = visibleColumnIds.indexOf(id);
566
+ if (index > -1) {
567
+ const newIds = visibleColumnIds.concat();
568
+ newIds.splice(index, 1);
569
+ setVisibleColumnIds(newIds);
570
+ } else {
571
+ setVisibleColumnIds(visibleColumnIds.concat(id));
572
+ }
573
+ }, [visibleColumnIds, setVisibleColumnIds]);
574
+ const isColumnVisible = React8.useCallback((id) => {
575
+ return visibleColumnIds.includes(id);
576
+ }, [visibleColumnIds]);
577
+ const Provider = provider;
578
+ const value = {
579
+ showFilters,
580
+ setShowFilters,
581
+ showMassActions,
582
+ setShowMassActions,
583
+ columns: columns ?? [],
584
+ filteredColumns,
585
+ toggleColumnVisibility,
586
+ isColumnVisible
587
+ };
588
+ return /* @__PURE__ */ jsx(Provider, { value, children });
589
+ }
590
+ function TableContainer({ initialPageSize, initialVisibleColumns, columns, filtersMethod = "reactRouter", children, ...props }) {
591
+ return /* @__PURE__ */ jsx("div", { w: "full", dflex: true, flexCol: true, ...props, children: /* @__PURE__ */ jsx(TableContextProvider, { initialPageSize, filtersMethod, children: /* @__PURE__ */ jsx(TableContainerContextProvider, { columns, initialVisibleColumns, children }) }) });
592
+ }
593
+ function TableCreateButton({ icon, children, ...props }) {
594
+ return /* @__PURE__ */ jsxs(ButtonBarDialogButton, { ...props, children: [
595
+ /* @__PURE__ */ jsx(Icon, { path: icon ?? mdiPlusThick }),
596
+ children
597
+ ] });
598
+ }
599
+ function TableFilterButton({ ...props }) {
600
+ return /* @__PURE__ */ jsxs(Popover, { side: "bottom-end", modal: true, children: [
601
+ /* @__PURE__ */ jsx(Button, { scheme: "dark", size: "sm", aspectRatio: "square", variant: "borderless", corners: "square", ...props, children: /* @__PURE__ */ jsx(Icon, { path: mdiFilter }) }),
602
+ /* @__PURE__ */ jsxs("div", { bgColor: "white", rounded: true, shadow: true, children: [
603
+ /* @__PURE__ */ jsx("div", { py: "3", hover_bgColor: "#ff0000", px: "5", hover_textColor: "white", children: "First Item" }),
604
+ /* @__PURE__ */ jsx("div", { py: "3", hover_bgColor: "#ff0000", px: "5", hover_textColor: "white", children: "Second Item" }),
605
+ /* @__PURE__ */ jsx("div", { py: "3", hover_bgColor: "#ff0000", px: "5", hover_textColor: "white", children: "Third Item" })
606
+ ] })
607
+ ] });
608
+ }
609
+ function TableTopBar({ title, breadcrumbs, children, ...props }) {
610
+ return /* @__PURE__ */ jsxs(
611
+ "div",
612
+ {
613
+ dflex: true,
614
+ flexRow: true,
615
+ alignItems: "center",
616
+ gap: "3",
617
+ p: "8",
618
+ ...props,
619
+ children: [
620
+ /* @__PURE__ */ jsxs("div", { children: [
621
+ title && /* @__PURE__ */ jsx("h2", { textSize: "x2", fontWeight: "600", textColor: "#3f4254", textTransform: "capitalize", children: title }),
622
+ breadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs, { breadcrumbs })
623
+ ] }),
624
+ /* @__PURE__ */ jsx("div", { flexGrow: true, children: "\xA0" }),
625
+ children
626
+ ]
627
+ }
628
+ );
629
+ }
630
+ function TableFilters({ form, initialValues, schema, processInput }) {
631
+ const { showFilters } = useContext();
632
+ const { getFilters, setFilters } = useTableContext();
633
+ const handleSubmit = React8.useCallback((values, actions) => {
634
+ const params = processInput?.(values) ?? values;
635
+ if (!isEqual(params, getFilters()))
636
+ setFilters(params);
637
+ actions.setSubmitting(false);
638
+ }, [setFilters, processInput]);
639
+ const handleReset = React8.useCallback((resetForm) => {
640
+ setFilters(initialValues);
641
+ resetForm();
642
+ }, [setFilters, initialValues]);
643
+ React8.useEffect(() => setFilters(initialValues), []);
644
+ return /* @__PURE__ */ jsx(Collapse, { in: showFilters, style: { overflow: showFilters ? "initial" : "hidden" }, children: /* @__PURE__ */ jsx("div", { p: "8", borderT: "px", borderB: "px", borderColor: "slate-100", children: /* @__PURE__ */ jsx(
645
+ FormProvider,
646
+ {
647
+ initialValues: mergeInitialFormValues(getFilters(), initialValues),
648
+ onSubmit: handleSubmit,
649
+ validationSchema: schema,
650
+ enableReinitialize: true,
651
+ children: (props) => /* @__PURE__ */ jsxs("div", { dflex: true, gap: "3", placeContent: "start", children: [
652
+ /* @__PURE__ */ jsx(FormRenderer, { flexRow: true, w: "auto", form }),
653
+ /* @__PURE__ */ jsx(Buttons2, { handleReset: () => handleReset(props.resetForm) })
654
+ ] })
655
+ }
656
+ ) }) });
657
+ }
658
+ function Buttons2({ handleReset }) {
659
+ return /* @__PURE__ */ jsxs("div", { dflex: true, gap: "3", children: [
660
+ /* @__PURE__ */ jsxs("div", { dflex: true, flexCol: true, children: [
661
+ /* @__PURE__ */ jsx(FieldLabel, { name: "", label: "\xA0" }),
662
+ /* @__PURE__ */ jsx(SubmitButton, { children: "Filter" })
663
+ ] }),
664
+ /* @__PURE__ */ jsxs("div", { dflex: true, flexCol: true, children: [
665
+ /* @__PURE__ */ jsx(FieldLabel, { name: "", label: "\xA0" }),
666
+ /* @__PURE__ */ jsx(Button, { scheme: "neutral", onClick: handleReset, children: "Reset" })
667
+ ] })
668
+ ] });
669
+ }
670
+ function ActionButton({
671
+ label,
672
+ buttonProps,
673
+ icon,
674
+ queryKey,
675
+ queryFn,
676
+ successMsg,
677
+ errorMsg,
678
+ invalidateParent
679
+ }) {
680
+ const mutation = invalidateParent ? useInvalidateParentMutation(queryFn, queryKey) : useApiMutation(queryFn, queryKey);
681
+ const mutate = useMutate(
682
+ mutation,
683
+ {
684
+ successMsg,
685
+ errorMsg
686
+ }
687
+ );
688
+ return /* @__PURE__ */ jsxs(
689
+ Button,
690
+ {
691
+ display: "flex",
692
+ alignItems: "center",
693
+ gap: "3",
694
+ ...buttonProps,
695
+ onClick: mutate,
696
+ disabled: mutation.isLoading,
697
+ children: [
698
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon }),
699
+ label
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ function TableMassActions({ actions }) {
705
+ const { ids } = useTableContext();
706
+ const showMassActions = ids && ids.length > 0;
707
+ return /* @__PURE__ */ jsx(Collapse, { in: showMassActions, style: { overflow: showMassActions ? "initial" : "hidden" }, children: /* @__PURE__ */ jsx("div", { dflex: true, gap: "3", flexWrap: true, alignItems: "center", px: "8", pt: "5", children: actions.map((action, index) => /* @__PURE__ */ jsx("div", { children: action.type == "button" && !action.showConfirmationDialog && /* @__PURE__ */ jsx(ActionButton, { label: action.label, queryFn: action.queryFn, queryKey: action.queryKey, buttonProps: action.buttonProps }) }, index)) }) });
708
+ }
709
+ var defaultErrorMsg = "Oops, something went wrong...";
710
+ function nonNullValues(data) {
711
+ if (data) {
712
+ const nonNullData = { ...data };
713
+ for (const key in data)
714
+ nonNullData[key] = nonNullData[key] ?? "";
715
+ return nonNullData;
716
+ }
717
+ return data;
718
+ }
719
+ function ItemEditDialog({
720
+ initialValues,
721
+ itemLabel,
722
+ queryId = "",
723
+ api,
724
+ queryFetchOptions,
725
+ querySaveOptions,
726
+ onSuccess,
727
+ onFetchError,
728
+ fetchErrorMsg = defaultErrorMsg,
729
+ onSaveError,
730
+ saveErrorMsg = defaultErrorMsg,
731
+ fetchToFormData,
732
+ formToQueryData,
733
+ invalidateQueriesOnSuccess = true,
734
+ invalidateQueryKey,
735
+ retryText = "Retry",
736
+ cancelLabel = "Cancel",
737
+ saveLabel,
738
+ size = "lg",
739
+ title,
740
+ form,
741
+ show,
742
+ onClose,
743
+ formikProps,
744
+ ...props
745
+ }) {
746
+ const { isInitialLoading, isFetching, data, isError, error, refetch } = useApiQuery(api.queryKey, api.get, queryId, {
747
+ enabled: !/*queryId == 0 || */
748
+ (queryId == "" || queryId == null || queryId == void 0),
749
+ // means than this query is only enabled if the id is defined
750
+ onError: onFetchError,
751
+ ...queryFetchOptions
752
+ });
753
+ const mutation = invalidateQueriesOnSuccess ? useInvalidateParentMutation(api.upsert, invalidateQueryKey ?? api.queryKey, querySaveOptions) : useApiMutation(api.upsert, api.queryKey, queryId, querySaveOptions);
754
+ const retry = React8.useCallback(() => refetch(), [refetch]);
755
+ const saveItem = React8.useCallback(async (item, actions) => {
756
+ mutation.reset();
757
+ const formItem = formToQueryData ? formToQueryData(item) : { ...item };
758
+ await mutation.mutateAsync(formItem).then((response) => {
759
+ if (onSuccess)
760
+ onSuccess(formItem, response);
761
+ else
762
+ toast.success(`${title ? title(formItem) : formItem.name ?? formItem.title} ${queryId ? "saved" : "created"}`);
763
+ onClose?.();
764
+ }).catch((error2) => {
765
+ console.error("on error", error2);
766
+ if (onSaveError)
767
+ onSaveError(item);
768
+ else
769
+ toast.error(`Couldn't save ${title ? title(formItem) : formItem.name ?? formItem.title}`);
770
+ actions.setSubmitting(false);
771
+ });
772
+ }, [mutation, formToQueryData, onSuccess, onSaveError, onClose]);
773
+ return /* @__PURE__ */ jsxs(
774
+ Modal,
775
+ {
776
+ size,
777
+ show,
778
+ onClose,
779
+ scheme: "light",
780
+ transition: true,
781
+ ...props,
782
+ children: [
783
+ /* @__PURE__ */ jsxs(Modal.Header, { children: [
784
+ !isInitialLoading && queryId && `Edit ${title ? title(data) : data?.["name"] ?? data?.["title"] ?? data?.["name"] ?? ""}`,
785
+ !queryId && `Create new ${itemLabel ?? "item"}`,
786
+ Array.isArray(form) && /* @__PURE__ */ jsx(Fragment, {})
787
+ ] }),
788
+ isInitialLoading && /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsx(QueryLoadingState, { minW: "72" }) }),
789
+ isError && /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsx(RetryOnError, { label: `${fetchErrorMsg} ${error}`, onClick: retry }) }),
790
+ !isInitialLoading && !isError && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
791
+ Formik,
792
+ {
793
+ initialValues: fetchToFormData && queryId && data ? fetchToFormData(nonNullValues(data)) : nonNullValues(initialValues(data)) ?? {},
794
+ onSubmit: saveItem,
795
+ ...formikProps,
796
+ children: ({ setFieldValue, dirty, handleSubmit, isValid, values }) => /* @__PURE__ */ jsxs(Fragment, { children: [
797
+ /* @__PURE__ */ jsx(Modal.Body, { pb: "6", children: /* @__PURE__ */ jsxs(Form, { children: [
798
+ React8.isValidElement(form) && form,
799
+ Array.isArray(form) && /* @__PURE__ */ jsx(FormRenderer, { form }),
800
+ isFunction(form) && /* @__PURE__ */ jsx(FormRenderer, { form: form(data ?? values) })
801
+ ] }) }),
802
+ /* @__PURE__ */ jsxs(Modal.Footer, { dflex: true, placeContent: "end", spaceX: "3", children: [
803
+ /* @__PURE__ */ jsx(
804
+ Button,
805
+ {
806
+ disabled: mutation.isLoading,
807
+ onClick: onClose,
808
+ variant: "borderless",
809
+ me: "2",
810
+ children: cancelLabel
811
+ }
812
+ ),
813
+ /* @__PURE__ */ jsx(
814
+ Button,
815
+ {
816
+ type: "submit",
817
+ disabled: !dirty || mutation.isLoading,
818
+ onClick: () => handleSubmit(),
819
+ children: saveLabel ? saveLabel : queryId ? "Update" : "Create"
820
+ }
821
+ )
822
+ ] })
823
+ ] })
824
+ }
825
+ ) }),
826
+ mutation.isLoading && /* @__PURE__ */ jsx(ModalLoadingOverlay, {})
827
+ ]
828
+ }
829
+ );
830
+ }
831
+ function QueryWrapperDialog({ api, fn, transformFn, config, onClose, queryId, invalidateQueryKey }) {
832
+ const { data, isFetching } = useApiQuery(
833
+ api.queryKey,
834
+ fn === "get" || fn === "getTransformed" ? api.get : api.getAll,
835
+ void 0,
836
+ { retryOnMount: false, refetchOnMount: false, refetchOnWindowFocus: false }
837
+ );
838
+ const transformedData = React8.useMemo(() => {
839
+ if (data && (fn === "getTransformed" || fn === "getAllTransformed")) {
840
+ if (!transformFn)
841
+ console.warn(`QueryWrapperDialog: you forgot to pass transformFn as parameter for fn ${fn}`);
842
+ return transformFn?.(data);
843
+ }
844
+ return data;
845
+ }, [data, fn, transformFn]);
846
+ if (isFetching)
847
+ return null;
848
+ return /* @__PURE__ */ jsx(ItemEditDialog, { ...config(transformedData), queryId, invalidateQueryKey, show: true, onClose });
849
+ }
850
+ function MultiQueryWrapperDialog({ queries, config, onClose, queryId, invalidateQueryKey }) {
851
+ const { data, isFetching, isError } = useApiQueries(queries.map((q) => ({
852
+ queryKey: q.api.queryKey,
853
+ queryFn: q.fn == "get" ? q.api.get : q.api.getAll,
854
+ queryOptions: {
855
+ cacheTime: q.cache === false ? 0 : void 0,
856
+ staleTime: q.cache === false ? 0 : void 0
857
+ }
858
+ })));
859
+ const transformedData = React8.useMemo(() => {
860
+ return data?.map((d, index) => queries[index]?.transformFn ? queries[index]?.transformFn?.(d) : d);
861
+ }, [data, queries]);
862
+ return /* @__PURE__ */ jsx(ItemEditDialog, { isPreloading: isFetching, ...config(...transformedData), queryId, invalidateQueryKey, show: true, onClose });
863
+ }
864
+ function DialogRenderer({ config, onClose, invalidateQueryKey, queryId }) {
865
+ const { type, ...props } = config;
866
+ if (config.type === "dialog")
867
+ return /* @__PURE__ */ jsx(ItemEditDialog, { ...props, queryId, invalidateQueryKey, show: true, onClose });
868
+ if (config.type === "query")
869
+ return /* @__PURE__ */ jsx(QueryWrapperDialog, { ...props, queryId, invalidateQueryKey, onClose });
870
+ if (config.type === "multiQuery")
871
+ return /* @__PURE__ */ jsx(MultiQueryWrapperDialog, { ...props, queryId, invalidateQueryKey, onClose });
872
+ return null;
873
+ }
874
+ function RefreshButton({ queryKey }) {
875
+ const invalidate = useInvalidateQuery(queryKey);
876
+ useHotkeys("ctrl+r", () => invalidate(), { preventDefault: true }, [invalidate]);
877
+ return /* @__PURE__ */ jsx(ButtonBarButton, { scheme: "dark", size: "sm", aspectRatio: "square", variant: "borderless", onClick: invalidate, children: /* @__PURE__ */ jsx(Icon, { path: mdiRefresh }) });
878
+ }
879
+ function ItemDeleteDialog({
880
+ title,
881
+ actionButtonLabel,
882
+ closeActionButtonLabel = "Cancel",
883
+ itemLabel,
884
+ queryId = "",
885
+ api,
886
+ apiFn,
887
+ invalidateQueriesOnSuccess = true,
888
+ invalidateQueryKey,
889
+ size = "lg",
890
+ md_boxSizing,
891
+ msg,
892
+ show,
893
+ onClose,
894
+ onSuccess,
895
+ ...props
896
+ }) {
897
+ const fn = apiFn ? api[apiFn] : api.delete;
898
+ const mutation = invalidateQueriesOnSuccess ? useInvalidateParentMutation(fn, invalidateQueryKey ?? api.queryKey) : useApiMutation(fn, api.queryKey);
899
+ const mutate = useMutate(mutation, { onSuccess: () => {
900
+ onClose?.();
901
+ onSuccess?.();
902
+ } });
903
+ const handleDelete = React8.useCallback(() => mutate(queryId), [mutate, queryId]);
904
+ return /* @__PURE__ */ jsxs(
905
+ Modal,
906
+ {
907
+ size,
908
+ show,
909
+ onClose,
910
+ scheme: "danger",
911
+ variant: "glass",
912
+ transition: true,
913
+ ...props,
914
+ children: [
915
+ /* @__PURE__ */ jsx(Modal.Header, { children: title || `Delete ${itemLabel}` }),
916
+ /* @__PURE__ */ jsxs(Modal.Body, { pb: "6", children: [
917
+ !msg && `Do you really want to delete ${itemLabel}?`,
918
+ msg && runIfFn(msg, itemLabel)
919
+ ] }),
920
+ /* @__PURE__ */ jsxs(Modal.Footer, { dflex: true, placeContent: "end", spaceX: "3", children: [
921
+ /* @__PURE__ */ jsx(
922
+ Button,
923
+ {
924
+ disabled: mutation.isPending,
925
+ onClick: onClose,
926
+ variant: "borderless",
927
+ scheme: "dark",
928
+ me: "2",
929
+ children: closeActionButtonLabel
930
+ }
931
+ ),
932
+ /* @__PURE__ */ jsx(Button, { scheme: "danger", disabled: mutation.isPending, onClick: handleDelete, children: actionButtonLabel || "Delete" })
933
+ ] }),
934
+ mutation.isPending && /* @__PURE__ */ jsx(ModalLoadingOverlay, {})
935
+ ]
936
+ }
937
+ );
938
+ }
939
+ var [provider2, useContext2] = createContext();
940
+ function TableViewProvider({ editView, deleteItem, queryKey, children }) {
941
+ const openLink = useOpenLink();
942
+ const navigate = useNavigate();
943
+ const [dialog, setDialog] = React8.useState(null);
944
+ const onCloseDialog = React8.useCallback(() => setDialog(null), [setDialog]);
945
+ const onAction = React8.useCallback((action, item) => {
946
+ switch (action.type) {
947
+ case "view": {
948
+ navigate(runIfFn(action.path, item));
949
+ break;
950
+ }
951
+ case "link": {
952
+ openLink(`${AppEnv.websiteUrl()}/${runIfFn(action.path, item)}`);
953
+ break;
954
+ }
955
+ case "edit": {
956
+ const editConfig = runIfFn(editView, item);
957
+ if (editConfig) {
958
+ if (editConfig.type == "customDialog") {
959
+ setDialog(editConfig.render({ show: true, onClose: onCloseDialog }));
960
+ } else {
961
+ setDialog(
962
+ /* @__PURE__ */ jsx(
963
+ DialogRenderer,
964
+ {
965
+ onClose: onCloseDialog,
966
+ config: editConfig,
967
+ queryId: item.id,
968
+ invalidateQueryKey: queryKey
969
+ }
970
+ )
971
+ );
972
+ }
973
+ }
974
+ break;
975
+ }
976
+ case "delete": {
977
+ const deleteConfig = runIfFn(deleteItem, item);
978
+ setDialog(
979
+ /* @__PURE__ */ jsx(
980
+ ItemDeleteDialog,
981
+ {
982
+ show: true,
983
+ onClose: onCloseDialog,
984
+ queryId: item.id,
985
+ invalidateQueryKey: queryKey,
986
+ msg: item.msg,
987
+ ...deleteConfig,
988
+ itemLabel: runIfFn(deleteConfig.itemLabel, item)
989
+ }
990
+ )
991
+ );
992
+ break;
993
+ }
994
+ }
995
+ }, [navigate, editView, deleteItem]);
996
+ const Provider = provider2;
997
+ const value = {
998
+ dialog,
999
+ onAction
1000
+ };
1001
+ return /* @__PURE__ */ jsx(Provider, { value, children });
1002
+ }
1003
+ function TableRowPublishPostButton({ id, api, status, invalidateQueryKey, ...props }) {
1004
+ const isDraft = status == "draft";
1005
+ const mutation = useInvalidateParentMutation(isDraft ? api.publish : api.unpublish, invalidateQueryKey ?? api.queryKey, { networkMode: "always" });
1006
+ const publish = React8.useCallback((event) => {
1007
+ event?.preventDefault();
1008
+ event?.stopPropagation();
1009
+ mutation.reset();
1010
+ mutation.mutateAsync(id).then(() => toast.success(isDraft ? "Published!" : "Unpublished!")).catch((error) => toast.error(`Error: ${error}`));
1011
+ }, [mutation, id]);
1012
+ return /* @__PURE__ */ jsx(Button, { variant: "borderless", corners: "square", scheme: "dark", onClick: publish, ...props, children: /* @__PURE__ */ jsx(Icon, { path: isDraft ? mdiPublish : mdiPublishOff, size: "sm" }) });
1013
+ }
1014
+ function TableRowActionButton({ icon, children, ...props }) {
1015
+ return /* @__PURE__ */ jsxs(
1016
+ Button,
1017
+ {
1018
+ dflex: true,
1019
+ alignContent: "center",
1020
+ placeContent: "center",
1021
+ py: "2.5",
1022
+ px: "3",
1023
+ h: "full",
1024
+ size: "lg",
1025
+ variant: "borderless",
1026
+ corners: "square",
1027
+ gap: "2",
1028
+ ...props,
1029
+ children: [
1030
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon, size: "sm" }),
1031
+ children
1032
+ ]
1033
+ }
1034
+ );
1035
+ }
1036
+ function TableRowActionsView({ row, onAction, rowActions, api, queryKey }) {
1037
+ const item = row.original;
1038
+ return /* @__PURE__ */ jsx("div", { dflex: true, w: "full", alignItems: "stretch", placeContent: "end", h: "full", children: runIfFn(rowActions, item)?.map(
1039
+ (action, index) => /* @__PURE__ */ jsxs(React8.Fragment, { children: [
1040
+ action.type === "publish" && /* @__PURE__ */ jsx(TableRowPublishPostButton, { id: item.id, api: action.api ?? api, status: item.status, invalidateQueryKey: queryKey }),
1041
+ action.type == "custom" && /* @__PURE__ */ jsx(Fragment, { children: action.component(item, queryKey, action.icon, action.label) }),
1042
+ !["publish", "custom"].includes(action.type) && /* @__PURE__ */ jsx(ActionButton2, { onClick: () => onAction(action, item), scheme: schemes[action.type], children: /* @__PURE__ */ jsx(Icon, { path: icons[action.type], size: "sm" }) })
1043
+ ] }, index)
1044
+ ) });
1045
+ }
1046
+ function ActionButton2({ onClick, ...props }) {
1047
+ const handleClick = React8.useCallback((event) => {
1048
+ event?.preventDefault();
1049
+ event?.stopPropagation();
1050
+ onClick?.(event);
1051
+ }, [onClick]);
1052
+ return /* @__PURE__ */ jsx(
1053
+ TableRowActionButton,
1054
+ {
1055
+ onClick: handleClick,
1056
+ ...props
1057
+ }
1058
+ );
1059
+ }
1060
+ var icons = {
1061
+ "link": mdiOpenInNew,
1062
+ "view": mdiEye,
1063
+ "edit": mdiPencil,
1064
+ "delete": mdiDelete,
1065
+ "publish": mdiDelete,
1066
+ "custom": ""
1067
+ };
1068
+ var schemes = {
1069
+ "link": "dark",
1070
+ "view": "dark",
1071
+ "edit": "dark",
1072
+ "delete": "dark",
1073
+ "publish": "dark",
1074
+ "custom": "dark"
1075
+ };
1076
+ function useTableProps(api, table, rowActions, queryParams) {
1077
+ const navigate = useNavigate();
1078
+ const nextRouter = useRouter();
1079
+ const openLink = useOpenLink();
1080
+ const { onAction } = useContext2();
1081
+ const { onRowClick, columns: c, ...props } = table;
1082
+ const onRowClickHandler = React8.useCallback((item) => {
1083
+ const config = runIfFn(onRowClick, item);
1084
+ if (config) {
1085
+ switch (config.type) {
1086
+ case "navigate": {
1087
+ navigate(runIfFn(config.path, item) ?? "");
1088
+ break;
1089
+ }
1090
+ case "nextpush": {
1091
+ nextRouter.push(runIfFn(config.path, item));
1092
+ break;
1093
+ }
1094
+ case "link": {
1095
+ openLink(`${AppEnv.websiteUrl()}/${runIfFn(config.path, item)}`);
1096
+ break;
1097
+ }
1098
+ }
1099
+ }
1100
+ }, [navigate, onRowClick]);
1101
+ const columns = React8.useMemo(() => {
1102
+ const columns2 = table.columns.concat([]);
1103
+ if (rowActions) {
1104
+ columns2.push(
1105
+ createColumnHelper().display(
1106
+ {
1107
+ id: "actions",
1108
+ header: "Actions",
1109
+ cell: (props2) => /* @__PURE__ */ jsx(TableRowActionsView, { row: props2.row, onAction, rowActions, api, queryKey: api.queryKey })
1110
+ }
1111
+ )
1112
+ );
1113
+ }
1114
+ return columns2;
1115
+ }, [table, onAction, rowActions, api]);
1116
+ const tableProps = { ...props };
1117
+ tableProps.columns = columns;
1118
+ tableProps.onRowClick = onRowClickHandler;
1119
+ tableProps.queryKey = api.queryKey;
1120
+ tableProps.queryFilters = queryParams;
1121
+ tableProps.queryFn = api.search;
1122
+ return tableProps;
1123
+ }
1124
+ function useId(queryField) {
1125
+ const params = useParams();
1126
+ if (queryField === void 0)
1127
+ return void 0;
1128
+ const { [queryField]: id } = params;
1129
+ return id;
1130
+ }
1131
+ function TableView({ queryField, title, subtitle, screen, ...props }) {
1132
+ const id = useId(queryField);
1133
+ const _screen = runIfFn(screen, id);
1134
+ return /* @__PURE__ */ jsx(PageContainer, { bgColor: "white", ...props, children: /* @__PURE__ */ jsx(TableContainer, { columns: _screen.table.columns, initialVisibleColumns: _screen.table.initialVisibleColumns, filtersMethod: _screen.table.filtersMethod, children: /* @__PURE__ */ jsx(TT, { id, title, subtitle, screen: _screen }) }) });
1135
+ }
1136
+ function TT({ id, title, subtitle, screen }) {
1137
+ const { setRowSelection } = useTableContext();
1138
+ const { api, table, filters, massActions, buttonBar, rowActions, createView, editView, deleteItem, breadcrumbs } = screen;
1139
+ const tableApi = runIfFn(api, id ?? "");
1140
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1141
+ /* @__PURE__ */ jsx(TableTopBar, { title, breadcrumbs, children: /* @__PURE__ */ jsx(TableButtonBar, { buttonBar, createView, editView, api: tableApi, queryKey: tableApi.queryKey, children: filters && /* @__PURE__ */ jsx(TableFilterButton, {}) }) }),
1142
+ filters && /* @__PURE__ */ jsx(
1143
+ TableFilters,
1144
+ {
1145
+ form: filters.form,
1146
+ initialValues: filters.initialValues,
1147
+ schema: filters.schema,
1148
+ processInput: filters.processInput
1149
+ }
1150
+ ),
1151
+ massActions && /* @__PURE__ */ jsx(TableMassActions, { actions: massActions.items }),
1152
+ /* @__PURE__ */ jsxs(TableViewProvider, { queryKey: tableApi.queryKey, editView, deleteItem, children: [
1153
+ /* @__PURE__ */ jsx(TableWrapper, { table: { ...table, onSelectionChange: setRowSelection }, rowActions, api: tableApi, subtitle, queryParams: screen.queryParams }),
1154
+ /* @__PURE__ */ jsx(TableDialogManager, {})
1155
+ ] })
1156
+ ] });
1157
+ }
1158
+ function TableButtonBar({ buttonBar, queryKey, createView, editView, api, children }) {
1159
+ const createDialogFn = React8.useCallback((data) => {
1160
+ return (onClose) => {
1161
+ const view = runIfFn(createView, data) ?? runIfFn(editView, null);
1162
+ if (view.type == "customDialog")
1163
+ return view.render({ show: true, onClose });
1164
+ else
1165
+ return /* @__PURE__ */ jsx(DialogRenderer, { config: view, onClose, invalidateQueryKey: api.queryKey });
1166
+ };
1167
+ }, [createView, editView, api]);
1168
+ return /* @__PURE__ */ jsxs(ButtonBar, { children: [
1169
+ /* @__PURE__ */ jsx(RefreshButton, { queryKey }),
1170
+ buttonBar && buttonBar.map(
1171
+ (button, index) => /* @__PURE__ */ jsxs(React8.Fragment, { children: [
1172
+ button.type === "create" && editView && /* @__PURE__ */ jsx(
1173
+ TableCreateButton,
1174
+ {
1175
+ buildDialog: createDialogFn(button.data),
1176
+ icon: button.icon,
1177
+ children: button.label
1178
+ }
1179
+ ),
1180
+ button.type === "invalidate" && /* @__PURE__ */ jsx(InvalidateButton, { pathOrPermalink: button.pathOrPermalink }),
1181
+ button.type === "custom" && button.render()
1182
+ ] }, index)
1183
+ ),
1184
+ children
1185
+ ] });
1186
+ }
1187
+ function TableWrapper({ table, subtitle, rowActions, api, queryParams }) {
1188
+ const tableProps = useTableProps(api, table, rowActions, queryParams);
1189
+ const _subtitle = React8.useMemo(() => {
1190
+ if (!subtitle)
1191
+ return void 0;
1192
+ return (data) => {
1193
+ return /* @__PURE__ */ jsx("div", { textSize: "lg", textColor: "#475569", fontWeight: "600", children: isFunction(subtitle) ? subtitle(data) : subtitle });
1194
+ };
1195
+ }, [subtitle]);
1196
+ return /* @__PURE__ */ jsx(Table, { ...tableProps, p: "8", title: _subtitle });
1197
+ }
1198
+ function TableDialogManager() {
1199
+ const { dialog } = useContext2();
1200
+ return dialog;
1201
+ }
1202
+ function useQueries(queries) {
1203
+ const params = useParams();
1204
+ const { data, isFetching, isError } = useApiQueries(queries.map((q) => {
1205
+ if (!q.queryField) {
1206
+ const api2 = runIfFn(q.api);
1207
+ return {
1208
+ queryKey: api2.queryKey,
1209
+ queryFn: q.fn == "get" ? api2.get : api2.getAll,
1210
+ queryParam: q.params
1211
+ };
1212
+ }
1213
+ const id = params[q.queryField];
1214
+ const api = runIfFn(q.api, id);
1215
+ return {
1216
+ queryKey: api.queryKey,
1217
+ queryFn: q.fn == "get" ? api.get : api.getAll,
1218
+ queryParam: q.fn == "getAll" ? q.params : isFunction(q.api) ? void 0 : id
1219
+ };
1220
+ }));
1221
+ let transformedData = void 0;
1222
+ if (!isFetching && !isError) {
1223
+ transformedData = data?.map((d, index) => queries[index].transformFn ? queries[index].transformFn?.(d) : d);
1224
+ }
1225
+ return { data: transformedData, isFetching, isError };
1226
+ }
1227
+ function useInvalidate(queries) {
1228
+ const queryClient = useQueryClient();
1229
+ const params = useParams();
1230
+ const invalidate = React8.useCallback(
1231
+ () => {
1232
+ const queryKeys = [];
1233
+ queries.forEach((q) => {
1234
+ if (!q.queryField || !isFunction(q.api)) {
1235
+ queryKeys.push(q.api.queryKey);
1236
+ } else {
1237
+ const id = params[q.queryField];
1238
+ const api = runIfFn(q.api, id);
1239
+ queryKeys.push(api.queryKey);
1240
+ }
1241
+ });
1242
+ queryClient.invalidateQueries({ predicate: (query) => queryKeys.includes(query.queryKey) });
1243
+ },
1244
+ [queries, queryClient, params]
1245
+ );
1246
+ return invalidate;
1247
+ }
1248
+ function MultiQueryWrapper({
1249
+ queries,
1250
+ config,
1251
+ tabbed,
1252
+ ...props
1253
+ }) {
1254
+ const { data, isFetching, isError } = useQueries(queries);
1255
+ const invalidate = useInvalidate(queries);
1256
+ if (isFetching)
1257
+ return /* @__PURE__ */ jsx(QueryLoadingState, { w: "full", h: "100vh" });
1258
+ if (isError || !data)
1259
+ return /* @__PURE__ */ jsx(RetryOnError, { p: "0", w: "full", h: "100vh", onClick: invalidate });
1260
+ return /* @__PURE__ */ jsx(ScreenRenderer, { config: config(...data), tabbed, ...props });
1261
+ }
1262
+ function ScreenRenderer({ config, tabbed, ...props }) {
1263
+ if (config.type === "table")
1264
+ return /* @__PURE__ */ jsx(TableView, { ...config, ...props });
1265
+ if (config.type === "tabbed")
1266
+ return /* @__PURE__ */ jsx(TabbedView, { ...config, ...props });
1267
+ if (config.type === "details")
1268
+ return /* @__PURE__ */ jsx(DetailsView, { ...config, tabbed, ...props });
1269
+ if (config.type === "query")
1270
+ return /* @__PURE__ */ jsx(QueryWrapper, { ...config, tabbed, ...props });
1271
+ if (config.type === "multiQuery")
1272
+ return /* @__PURE__ */ jsx(MultiQueryWrapper, { ...config, tabbed, ...props });
1273
+ return /* @__PURE__ */ jsx(Fragment, {});
1274
+ }
1275
+ function Content({ ...props }) {
1276
+ return /* @__PURE__ */ jsx("div", { w: "100%", h: "100%", overflowY: "auto", ...props, children: /* @__PURE__ */ jsx(Outlet, {}) });
1277
+ }
1278
+ function SelectedIndicator({ darkMode }) {
1279
+ return /* @__PURE__ */ jsx(
1280
+ "div",
1281
+ {
1282
+ position: "absolute",
1283
+ bgColor: darkMode ? "white" : "black",
1284
+ bgOpacity: "90",
1285
+ w: "0.5",
1286
+ h: "6",
1287
+ top: "1.5",
1288
+ start: "-4"
1289
+ }
1290
+ );
1291
+ }
1292
+ function MenuButton({ depth, darkMode, icon, selected, children, ...props }) {
1293
+ return /* @__PURE__ */ jsxs(
1294
+ Button,
1295
+ {
1296
+ as: "li",
1297
+ minH: "8",
1298
+ ms: `${(depth ?? 0) * 2}`,
1299
+ p: "2",
1300
+ font: "title",
1301
+ textColor: darkMode ? "white" : "black",
1302
+ fontWeight: "600",
1303
+ rounded: "lg",
1304
+ textSize: "sm",
1305
+ variant: "borderless",
1306
+ hover_bgColor: darkMode ? "white" : "black",
1307
+ hover_bgOpacity: "10",
1308
+ hover_textColor: darkMode ? "white" : "zinc-800",
1309
+ cursor: "pointer",
1310
+ dflex: true,
1311
+ alignItems: "center",
1312
+ gap: "3",
1313
+ ...props,
1314
+ children: [
1315
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon, opacity: selected ? "100" : "60" }),
1316
+ children
1317
+ ]
1318
+ }
1319
+ );
1320
+ }
1321
+ function MenuItem({ icon, path, depth, darkMode, subMenu, ...props }) {
1322
+ const location = useLocation();
1323
+ const selected = path == "/" ? location.pathname == "/" : location.pathname.startsWith(path.startsWith("/") ? path : `/${path}`);
1324
+ const match = useMatch("/" + path) != null;
1325
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1326
+ /* @__PURE__ */ jsxs(Link$1, { to: path, style: { position: "relative" }, children: [
1327
+ /* @__PURE__ */ jsx(
1328
+ MenuButton,
1329
+ {
1330
+ depth,
1331
+ darkMode,
1332
+ icon,
1333
+ selected,
1334
+ ...props
1335
+ }
1336
+ ),
1337
+ match && /* @__PURE__ */ jsx(SelectedIndicator, { darkMode })
1338
+ ] }),
1339
+ subMenu?.map((item, index) => /* @__PURE__ */ jsx(
1340
+ MenuItem,
1341
+ {
1342
+ icon: item.icon,
1343
+ path: item.path,
1344
+ depth: (depth ?? 0) + 1,
1345
+ darkMode,
1346
+ subMenu: item.children,
1347
+ children: item.label
1348
+ },
1349
+ index
1350
+ ))
1351
+ ] });
1352
+ }
1353
+ function NextMenuItem({ icon, path, depth, darkMode, subMenu, ...props }) {
1354
+ const { pathname } = useRouter();
1355
+ const selected = path == "/" ? pathname == "/" : pathname.startsWith(path.startsWith("/") ? path : `/${path}`);
1356
+ const match = path == "/" ? pathname == "/" : pathname == (path.startsWith("/") ? path : `/${path}`);
1357
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1358
+ /* @__PURE__ */ jsxs(Link, { href: path, style: { position: "relative" }, children: [
1359
+ /* @__PURE__ */ jsx(
1360
+ MenuButton,
1361
+ {
1362
+ depth,
1363
+ darkMode,
1364
+ icon,
1365
+ selected,
1366
+ ...props
1367
+ }
1368
+ ),
1369
+ match && /* @__PURE__ */ jsx(SelectedIndicator, { darkMode })
1370
+ ] }),
1371
+ subMenu?.map((item, index) => /* @__PURE__ */ jsx(
1372
+ NextMenuItem,
1373
+ {
1374
+ icon: item.icon,
1375
+ path: item.path,
1376
+ depth: (depth ?? 0) + 1,
1377
+ darkMode,
1378
+ subMenu: item.children,
1379
+ children: item.label
1380
+ },
1381
+ index
1382
+ ))
1383
+ ] });
1384
+ }
1385
+ function Menu({ darkMode, config, useNextRouter, ...props }) {
1386
+ const Comp = useNextRouter ? NextMenuItem : MenuItem;
1387
+ return /* @__PURE__ */ jsx("ul", { ...props, children: config.map((item, index) => {
1388
+ if (item.type == "divider")
1389
+ return /* @__PURE__ */ jsx(Divider, { title: item.label }, index);
1390
+ if (item.type == "item") {
1391
+ return /* @__PURE__ */ jsx(
1392
+ Comp,
1393
+ {
1394
+ icon: item.icon,
1395
+ path: item.path,
1396
+ depth: 0,
1397
+ darkMode,
1398
+ subMenu: item.children,
1399
+ children: item.label
1400
+ },
1401
+ index
1402
+ );
1403
+ }
1404
+ }) });
1405
+ }
1406
+ function Divider({ title }) {
1407
+ return /* @__PURE__ */ jsx(
1408
+ "div",
1409
+ {
1410
+ px: "2",
1411
+ mt: "5",
1412
+ mb: "2",
1413
+ opacity: "75",
1414
+ textTransform: "capitalize",
1415
+ letterSpacing: "widest",
1416
+ fontWeight: "700",
1417
+ textSize: "xs",
1418
+ children: title
1419
+ }
1420
+ );
1421
+ }
1422
+ function UserBlock({ color, darkMode, menuConfig, path }) {
1423
+ const { isLoading, user } = useSessionUser();
1424
+ const navigate = useNavigate();
1425
+ const handleClick = React8.useCallback(() => navigate(path), [navigate, path]);
1426
+ if (isLoading)
1427
+ return null;
1428
+ return /* @__PURE__ */ jsxs(
1429
+ "div",
1430
+ {
1431
+ dflex: true,
1432
+ alignItems: "center",
1433
+ border: "0.5",
1434
+ borderColor: `${color}-${darkMode ? "800" : "200"}`,
1435
+ ps: "3",
1436
+ py: "1.5",
1437
+ textSize: "md",
1438
+ rounded: "lg",
1439
+ hover_bgColor: `${color}-${darkMode ? "800" : "200"}`,
1440
+ cursor: "pointer",
1441
+ textColor: darkMode ? "white" : "slate-800",
1442
+ onClick: handleClick,
1443
+ children: [
1444
+ /* @__PURE__ */ jsx(Avatar, { size: "sm", src: user?.media?.url ?? "", name: `${user?.firstname} ${user?.lastname}` }),
1445
+ /* @__PURE__ */ jsx("span", { flexGrow: true, ms: "2", children: `${capitalize(user?.firstname || user?.lastname || "")}` }),
1446
+ /* @__PURE__ */ jsx(
1447
+ IconButton,
1448
+ {
1449
+ variant: "borderless",
1450
+ corners: "pill",
1451
+ scheme: "dark",
1452
+ textColor: darkMode ? "white" : "slate-800",
1453
+ hover_textColor: darkMode ? "white" : "slate-800",
1454
+ hover_bgColor: `${color}-${darkMode ? "900" : "200"}`,
1455
+ icon: mdiCog,
1456
+ onClick: handleClick
1457
+ }
1458
+ ),
1459
+ /* @__PURE__ */ jsx(OverflowMenu, { color, darkMode, menuConfig })
1460
+ ]
1461
+ }
1462
+ );
1463
+ }
1464
+ function OverflowMenu({ color, darkMode, menuConfig }) {
1465
+ const [showPopup, setShowPopup] = React8.useState(false);
1466
+ const navigate = useNavigate();
1467
+ const logout = useSessionLogout(false);
1468
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
1469
+ Popup,
1470
+ {
1471
+ show: showPopup,
1472
+ position: "relative",
1473
+ side: "bottom-end",
1474
+ onClick: (e) => {
1475
+ e.preventDefault();
1476
+ e.stopPropagation();
1477
+ setShowPopup((show) => !show);
1478
+ },
1479
+ onHide: () => setShowPopup(false),
1480
+ children: [
1481
+ /* @__PURE__ */ jsx(
1482
+ IconButton,
1483
+ {
1484
+ icon: mdiDotsVertical,
1485
+ variant: "borderless",
1486
+ corners: "pill",
1487
+ scheme: "dark",
1488
+ textColor: darkMode ? "white" : "slate-800",
1489
+ hover_textColor: darkMode ? "white" : "slate-800",
1490
+ hover_bgColor: `${color}-${darkMode ? "900" : "200"}`
1491
+ }
1492
+ ),
1493
+ /* @__PURE__ */ jsxs(
1494
+ "div",
1495
+ {
1496
+ bgColor: "white",
1497
+ rounded: "sm",
1498
+ overflow: "hidden",
1499
+ shadow: true,
1500
+ mt: "1",
1501
+ border: "px",
1502
+ borderColor: "gray-200",
1503
+ divideColor: "gray-200",
1504
+ divideY: "px",
1505
+ minW: "40",
1506
+ children: [
1507
+ menuConfig && menuConfig.length > 0 && menuConfig.map((item, index) => {
1508
+ if (item.type == "item") {
1509
+ return /* @__PURE__ */ jsx(MenuItem2, { icon: item.icon, onClick: () => navigate(item.path), children: item.label }, index);
1510
+ }
1511
+ return null;
1512
+ }),
1513
+ /* @__PURE__ */ jsx(MenuItem2, { icon: mdiLogout, onClick: logout, children: "Logout" })
1514
+ ]
1515
+ }
1516
+ )
1517
+ ]
1518
+ }
1519
+ ) });
1520
+ }
1521
+ function MenuItem2({ icon, onClick, children, ...props }) {
1522
+ const handleClick = React8.useCallback((e) => {
1523
+ e.preventDefault();
1524
+ e.stopPropagation();
1525
+ onClick?.(e);
1526
+ }, []);
1527
+ return /* @__PURE__ */ jsxs(
1528
+ Button,
1529
+ {
1530
+ variant: "borderless",
1531
+ scheme: "dark",
1532
+ size: "sm",
1533
+ alignItems: "center",
1534
+ dflex: true,
1535
+ gap: "2",
1536
+ px: "2",
1537
+ py: "1.5",
1538
+ w: "full",
1539
+ onClick: handleClick,
1540
+ textColor: "slate-700",
1541
+ ...props,
1542
+ children: [
1543
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon, size: "md" }),
1544
+ children
1545
+ ]
1546
+ }
1547
+ );
1548
+ }
1549
+ function Sidebar({ show, logo, title, menuConfig, userMenuConfig, userSettingsPath, color, darkMode, ...props }) {
1550
+ return /* @__PURE__ */ jsxs(
1551
+ "div",
1552
+ {
1553
+ dflex: true,
1554
+ flexCol: true,
1555
+ w: "full",
1556
+ md_w: "64",
1557
+ minH: "screen",
1558
+ p: "0",
1559
+ textColor: darkMode ? "white" : "slate-800",
1560
+ ...props,
1561
+ children: [
1562
+ /* @__PURE__ */ jsxs(
1563
+ FlexCenter,
1564
+ {
1565
+ placeContent: "start",
1566
+ p: "4",
1567
+ font: "title",
1568
+ gap: "3",
1569
+ borderB: "px",
1570
+ borderBColor: "slate-900",
1571
+ borderOpacity: "5",
1572
+ children: [
1573
+ logo ?? /* @__PURE__ */ jsx(Logo, { width: 40, height: 40, darkMode }),
1574
+ /* @__PURE__ */ jsx("h1", { textSize: "md", children: title || AppEnv.appName() })
1575
+ ]
1576
+ }
1577
+ ),
1578
+ /* @__PURE__ */ jsx(Menu, { overflowY: "auto", flexGrow: "1", p: "4", darkMode, config: menuConfig }),
1579
+ /* @__PURE__ */ jsx("div", { p: "2", children: /* @__PURE__ */ jsx(UserBlock, { darkMode, color, menuConfig: userMenuConfig, path: userSettingsPath }) })
1580
+ ]
1581
+ }
1582
+ );
1583
+ }
1584
+ function Logo({ width, height, darkMode, ...props }) {
1585
+ return /* @__PURE__ */ jsx("div", { ...props, children: /* @__PURE__ */ jsx(
1586
+ Image,
1587
+ {
1588
+ src: `/logo_${darkMode ? "light" : "dark"}.png`,
1589
+ alt: AppEnv.appName() || "",
1590
+ width,
1591
+ height,
1592
+ priority: true,
1593
+ unoptimized: true
1594
+ }
1595
+ ) });
1596
+ }
1597
+ function AdminLayout({ color, darkMode, logo, title, menuConfig, userMenuConfig, userSettingsPath, ...props }) {
1598
+ return /* @__PURE__ */ jsxs(
1599
+ "div",
1600
+ {
1601
+ w: "full",
1602
+ h: "screen",
1603
+ dflex: true,
1604
+ flexRow: true,
1605
+ bgColor: `${color}-${darkMode ? "900" : "100"}`,
1606
+ ...props,
1607
+ children: [
1608
+ /* @__PURE__ */ jsx(
1609
+ LeftPanel,
1610
+ {
1611
+ color,
1612
+ darkMode,
1613
+ logo,
1614
+ title,
1615
+ menuConfig,
1616
+ userMenuConfig,
1617
+ userSettingsPath
1618
+ }
1619
+ ),
1620
+ /* @__PURE__ */ jsx("div", { w: "screen", py: "2", pe: "2", children: /* @__PURE__ */ jsx(Content, { bgColor: "white", rounded: "lg", shadow: true }) })
1621
+ ]
1622
+ }
1623
+ );
1624
+ }
1625
+ function LeftPanel({ color, darkMode, logo, title, menuConfig, userMenuConfig, userSettingsPath }) {
1626
+ const [isOpen, __, toggle] = useBoolean(true);
1627
+ useHotkeys("ctrl+t", () => toggle(), [toggle]);
1628
+ return /* @__PURE__ */ jsxs(
1629
+ "div",
1630
+ {
1631
+ ms: isOpen ? "0" : "-14.5rem",
1632
+ transition: "all",
1633
+ duration: "500",
1634
+ transform: true,
1635
+ children: [
1636
+ /* @__PURE__ */ jsx(
1637
+ Sidebar,
1638
+ {
1639
+ flexShrink: "0",
1640
+ color,
1641
+ darkMode,
1642
+ logo,
1643
+ title,
1644
+ menuConfig,
1645
+ userMenuConfig,
1646
+ userSettingsPath
1647
+ }
1648
+ ),
1649
+ /* @__PURE__ */ jsx(
1650
+ IconButton,
1651
+ {
1652
+ icon: mdiArrowLeft,
1653
+ transition: "all",
1654
+ duration: "500",
1655
+ transform: true,
1656
+ rotate: isOpen ? "0" : "180",
1657
+ position: "absolute",
1658
+ bottom: "14",
1659
+ end: "-5",
1660
+ size: "lg",
1661
+ corners: "pill",
1662
+ onClick: toggle,
1663
+ z: "100"
1664
+ }
1665
+ )
1666
+ ]
1667
+ }
1668
+ );
1669
+ }
1670
+ var defaultErrorMsg2 = "Oops, something went wrong...";
1671
+ function AttachDialog({
1672
+ queryId,
1673
+ queryKey,
1674
+ queryFetchFn,
1675
+ queryFetchAllKey,
1676
+ queryFetchAllFn,
1677
+ querySaveFn,
1678
+ matchKey,
1679
+ size = "lg",
1680
+ show,
1681
+ onClose,
1682
+ itemLabel,
1683
+ onSuccess,
1684
+ onFetchError,
1685
+ fetchErrorMsg = defaultErrorMsg2,
1686
+ onSaveError,
1687
+ saveErrorMsg = defaultErrorMsg2,
1688
+ invalidateQueriesOnSuccess = true,
1689
+ retryText = "Retry",
1690
+ cancelLabel = "Cancel",
1691
+ saveLabel,
1692
+ formikProps,
1693
+ getItemName,
1694
+ ...props
1695
+ }) {
1696
+ const queryClient = useQueryClient();
1697
+ const { data: attached, isInitialLoading: fetchLoading, isError: fetchError, refetch, error } = useApiQuery(queryKey, queryFetchFn);
1698
+ const { data, isInitialLoading: fetchAllLoading, isError: fetchAllError, refetch: refetchAll, error: errorAll } = useApiQuery(queryFetchAllKey, queryFetchAllFn);
1699
+ const [selectedResources, setSelectedResources] = React8.useState([]);
1700
+ const isLoading = fetchLoading || fetchAllLoading;
1701
+ const isError = fetchError || fetchAllError;
1702
+ const mutation = useApiMutation(querySaveFn, queryKey, queryId);
1703
+ const handleClick = React8.useCallback((event) => {
1704
+ const id = event?.currentTarget.dataset.id ?? "";
1705
+ const arr = selectedResources.concat([]);
1706
+ const i = selectedResources.indexOf(id);
1707
+ if (i != -1)
1708
+ arr.splice(i, 1);
1709
+ else
1710
+ arr.push(id);
1711
+ setSelectedResources(arr);
1712
+ }, [selectedResources, setSelectedResources]);
1713
+ const retry = React8.useCallback(() => {
1714
+ if (fetchError)
1715
+ refetch();
1716
+ if (fetchAllError)
1717
+ refetchAll();
1718
+ }, [refetch, refetchAll, fetchError, fetchAllError]);
1719
+ const saveItem = React8.useCallback(() => {
1720
+ mutation.reset();
1721
+ mutation.mutateAsync({ resources: selectedResources }).then((response) => {
1722
+ if (onSuccess)
1723
+ onSuccess(response);
1724
+ else
1725
+ toast.success(`${itemLabel} saved`);
1726
+ if (invalidateQueriesOnSuccess)
1727
+ queryClient.invalidateQueries({ queryKey });
1728
+ onClose?.();
1729
+ }).catch((error2) => {
1730
+ console.error("on error", error2);
1731
+ if (onSaveError)
1732
+ onSaveError();
1733
+ else
1734
+ toast.error(`Error adding ${itemLabel}`);
1735
+ });
1736
+ }, [mutation, queryId, onSuccess, queryClient, onSaveError, onClose]);
1737
+ const resources = React8.useMemo(() => {
1738
+ let r = [];
1739
+ if (attached && data) {
1740
+ r = [].concat(data);
1741
+ attached.forEach((attachedItem) => r.splice(r.findIndex((item) => item.id == attachedItem[matchKey]), 1));
1742
+ if (getItemName)
1743
+ r = r.map((item) => {
1744
+ return { ...item, name: getItemName(item) };
1745
+ });
1746
+ r = sortBy(r, ["name"]);
1747
+ }
1748
+ return r;
1749
+ }, [attached, data]);
1750
+ return /* @__PURE__ */ jsxs(
1751
+ Modal,
1752
+ {
1753
+ size,
1754
+ show,
1755
+ onClose,
1756
+ scheme: "light",
1757
+ transition: true,
1758
+ ...props,
1759
+ children: [
1760
+ /* @__PURE__ */ jsx(Modal.Header, { children: `Add ${itemLabel}` }),
1761
+ isLoading && /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsx(QueryLoadingState, { minW: "72" }) }),
1762
+ isError && /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsx(RetryOnError, { label: `${fetchErrorMsg} ${error}`, onClick: retry }) }),
1763
+ !isLoading && !isError && /* @__PURE__ */ jsxs(Fragment, { children: [
1764
+ /* @__PURE__ */ jsx(Modal.Body, { px: "0", pb: "6", maxH: "750px", overflow: "auto", children: /* @__PURE__ */ jsx("div", { dflex: true, flexCol: true, overflow: "auto", children: resources.map((item) => /* @__PURE__ */ jsx(
1765
+ ListItem,
1766
+ {
1767
+ label: item.name,
1768
+ value: item.id,
1769
+ "data-id": item.id,
1770
+ checked: selectedResources.includes(`${item.id}`),
1771
+ onClick: handleClick
1772
+ },
1773
+ item.id
1774
+ )) }) }),
1775
+ /* @__PURE__ */ jsxs(Modal.Footer, { dflex: true, placeContent: "end", spaceX: "3", children: [
1776
+ /* @__PURE__ */ jsx(
1777
+ Button,
1778
+ {
1779
+ disabled: mutation.isLoading,
1780
+ onClick: onClose,
1781
+ variant: "borderless",
1782
+ me: "2",
1783
+ children: cancelLabel
1784
+ }
1785
+ ),
1786
+ /* @__PURE__ */ jsx(
1787
+ Button,
1788
+ {
1789
+ type: "submit",
1790
+ disabled: selectedResources.length == 0 || mutation.isLoading,
1791
+ onClick: saveItem,
1792
+ children: saveLabel ? saveLabel : queryId ? "Update" : "Create"
1793
+ }
1794
+ )
1795
+ ] })
1796
+ ] }),
1797
+ mutation.isLoading && /* @__PURE__ */ jsx(ModalLoadingOverlay, {})
1798
+ ]
1799
+ }
1800
+ );
1801
+ }
1802
+ function ListItem({ label, value, checked, ...props }) {
1803
+ return /* @__PURE__ */ jsxs(
1804
+ "div",
1805
+ {
1806
+ dflex: true,
1807
+ alignItems: "center",
1808
+ hover_bgColor: "slate-100",
1809
+ px: "5",
1810
+ py: "2",
1811
+ cursor: "pointer",
1812
+ ...props,
1813
+ children: [
1814
+ /* @__PURE__ */ jsx("span", { flexGrow: true, children: label }),
1815
+ /* @__PURE__ */ jsx(Checkbox, { name: `resources.${value}`, value, checked })
1816
+ ]
1817
+ }
1818
+ );
1819
+ }
1820
+ function FormActionDialog({
1821
+ initialValues,
1822
+ itemLabel,
1823
+ queryId = "",
1824
+ queryKey,
1825
+ queryFn,
1826
+ queryOptions,
1827
+ onSuccess,
1828
+ successMsg,
1829
+ showSuccessMsg,
1830
+ onError,
1831
+ errorMsg,
1832
+ showErrorMsg,
1833
+ processInput,
1834
+ invalidateQueriesOnSuccess = true,
1835
+ cancelLabel = "Cancel",
1836
+ saveLabel = "Send",
1837
+ size = "lg",
1838
+ title,
1839
+ form,
1840
+ show,
1841
+ onClose,
1842
+ formikProps,
1843
+ ...props
1844
+ }) {
1845
+ const mutation = invalidateQueriesOnSuccess ? useInvalidateParentMutation(queryFn, queryKey, queryOptions) : useApiMutation(queryFn, queryKey, queryId, queryOptions);
1846
+ const mutate = useMutate(mutation, {
1847
+ onSuccess,
1848
+ successMsg,
1849
+ showSuccessMsg,
1850
+ onError,
1851
+ errorMsg,
1852
+ showErrorMsg,
1853
+ processInput
1854
+ });
1855
+ return /* @__PURE__ */ jsxs(
1856
+ Modal,
1857
+ {
1858
+ size,
1859
+ show,
1860
+ onClose,
1861
+ scheme: "light",
1862
+ transition: true,
1863
+ ...props,
1864
+ children: [
1865
+ /* @__PURE__ */ jsx(Modal.Header, { children: title }),
1866
+ /* @__PURE__ */ jsxs(
1867
+ FormProvider,
1868
+ {
1869
+ initialValues: initialValues ?? {},
1870
+ onSubmit: mutate,
1871
+ ...formikProps,
1872
+ children: [
1873
+ /* @__PURE__ */ jsxs(Modal.Body, { pb: "6", children: [
1874
+ React8.isValidElement(form) && form,
1875
+ Array.isArray(form) && /* @__PURE__ */ jsx(FormRenderer, { form })
1876
+ ] }),
1877
+ /* @__PURE__ */ jsxs(Modal.Footer, { dflex: true, placeContent: "end", spaceX: "3", children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ Button,
1880
+ {
1881
+ disabled: mutation.isLoading,
1882
+ onClick: onClose,
1883
+ variant: "borderless",
1884
+ me: "2",
1885
+ children: cancelLabel
1886
+ }
1887
+ ),
1888
+ /* @__PURE__ */ jsx(SubmitButton, { disabled: mutation.isLoading, children: saveLabel })
1889
+ ] })
1890
+ ]
1891
+ }
1892
+ ),
1893
+ mutation.isLoading && /* @__PURE__ */ jsx(ModalLoadingOverlay, {})
1894
+ ]
1895
+ }
1896
+ );
1897
+ }
1898
+ var PageStateContainer = React8.forwardRef(({ loading = false, children, ...props }, ref) => {
1899
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1900
+ loading && /* @__PURE__ */ jsx(QueryLoadingState, { w: "full", h: "100%" }),
1901
+ !loading && /* @__PURE__ */ jsx(Container, { ref, center: true, size: "x2", dflex: true, flexCol: true, gap: "8", ...props, children })
1902
+ ] });
1903
+ });
1904
+ function PageSubSectionTitle({ children, ...props }) {
1905
+ return /* @__PURE__ */ jsx("div", { trait: "typo.h6", mb: "3", ...props, children });
1906
+ }
1907
+ var labels = {
1908
+ draft: "Draft",
1909
+ published: "Published",
1910
+ pending: "Pending",
1911
+ approved: "Approved",
1912
+ partially_approved: "Partially Approved",
1913
+ rejected: "Rejected"
1914
+ };
1915
+ var schemes2 = {
1916
+ draft: "warning",
1917
+ published: "success",
1918
+ pending: "secondary",
1919
+ approved: "success",
1920
+ partially_approved: "warning",
1921
+ rejected: "danger"
1922
+ };
1923
+ function StatusBadge({ status, ...props }) {
1924
+ return /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "glass", whiteSpace: "nowrap", rounded: "full", px: "3", scheme: schemes2[status], ...props, children: labels[status] });
1925
+ }
1926
+ function TableRowViewButton({ path, ...props }) {
1927
+ const openPage = React8.useCallback((event) => {
1928
+ event?.preventDefault();
1929
+ event?.stopPropagation();
1930
+ window.open(`${AppEnv.websiteUrl()}/${path}`, "_blank");
1931
+ }, [path]);
1932
+ return /* @__PURE__ */ jsx(TableRowActionButton, { icon: mdiOpenInNew, onClick: openPage, ...props });
1933
+ }
1934
+ function TableRowNavigateButton({ path, ...props }) {
1935
+ const navigate = useNavigate();
1936
+ const handleClick = React8.useCallback((event) => {
1937
+ event?.preventDefault();
1938
+ event?.stopPropagation();
1939
+ navigate(`${path}`);
1940
+ }, [navigate, path]);
1941
+ return /* @__PURE__ */ jsx(TableRowActionButton, { icon: mdiEye, onClick: handleClick, ...props });
1942
+ }
1943
+ function TableRowActionDialogButton({ icon, children, ...props }) {
1944
+ return /* @__PURE__ */ jsxs(
1945
+ DialogButton,
1946
+ {
1947
+ dflex: true,
1948
+ alignContent: "center",
1949
+ placeContent: "center",
1950
+ py: "2.5",
1951
+ px: "3",
1952
+ h: "full",
1953
+ size: "lg",
1954
+ variant: "borderless",
1955
+ corners: "square",
1956
+ gap: "2",
1957
+ ...props,
1958
+ children: [
1959
+ icon && /* @__PURE__ */ jsx(Icon, { path: icon, size: "sm" }),
1960
+ children
1961
+ ]
1962
+ }
1963
+ );
1964
+ }
1965
+ function TableRowEditButton({ children, ...props }) {
1966
+ return /* @__PURE__ */ jsx(TableRowActionDialogButton, { icon: mdiPencil, ...props, children });
1967
+ }
1968
+ function TableRowDeleteButton({ children, ...props }) {
1969
+ return /* @__PURE__ */ jsx(TableRowActionDialogButton, { icon: mdiDelete, ...props, children });
1970
+ }
1971
+ function TableRowActionBar({
1972
+ publishId,
1973
+ viewPath,
1974
+ navigatePath,
1975
+ editDialog,
1976
+ deleteDialog,
1977
+ children,
1978
+ ...props
1979
+ }) {
1980
+ return /* @__PURE__ */ jsxs("div", { dflex: true, spaceX: "1", placeContent: "end", ...props, children: [
1981
+ viewPath && /* @__PURE__ */ jsx(TableRowViewButton, { path: viewPath }),
1982
+ navigatePath && /* @__PURE__ */ jsx(TableRowNavigateButton, { path: navigatePath }),
1983
+ editDialog && /* @__PURE__ */ jsx(TableRowEditButton, { buildDialog: editDialog }),
1984
+ deleteDialog && /* @__PURE__ */ jsx(TableRowDeleteButton, { buildDialog: deleteDialog }),
1985
+ children
1986
+ ] });
1987
+ }
1988
+ function TableRowPublishPostButton2({ id, api, status, invalidateQueryKey, ...props }) {
1989
+ const isDraft = status == "draft";
1990
+ const mutation = useInvalidateParentMutation(isDraft ? api.publish : api.unpublish, invalidateQueryKey ?? api.queryKey, { networkMode: "always" });
1991
+ const publish = React8.useCallback((event) => {
1992
+ event?.preventDefault();
1993
+ event?.stopPropagation();
1994
+ mutation.reset();
1995
+ mutation.mutateAsync(id).then(() => toast.success(isDraft ? "Published!" : "Unpublished!")).catch((error) => toast.error(`Error: ${error}`));
1996
+ }, [mutation, id]);
1997
+ return /* @__PURE__ */ jsx(TableRowActionButton, { icon: isDraft ? mdiPublish : mdiPublishOff, onClick: publish, ...props });
1998
+ }
1999
+
2000
+ export { AdminLayout, AttachDialog, Breadcrumbs, ButtonBar, ButtonBarButton, ButtonBarDialogButton, ButtonBarSubmitButton, DialogButton, FormActionDialog, InvalidateButton, ItemDeleteDialog, ItemEditDialog, Menu, NavigateButton, OrderCell, PageContainer, PageContentEditor, PageMain, PageQueryStateContainer, PageSectionTitle, PageSidebar, PageSidebarSection, PageStateContainer, PageSubSectionTitle, PageTabbedTopBar, PageTabbedTopBarProvider, PageTitle, PageTopBar, PageTopBarToolbar, PublishButton, ScreenRenderer, SectionTitle, StatusBadge, TableContainer, TableCreateButton, TableFilterButton, TableRowActionBar, TableRowActionButton, TableRowActionDialogButton, TableRowDeleteButton, TableRowEditButton, TableRowNavigateButton, TableRowPublishPostButton2 as TableRowPublishPostButton, TableRowViewButton, TableTopBar, UpdateButton, ViewButton };