@carlonicora/nextjs-jsonapi 1.115.0 → 1.117.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.
- package/dist/{AssistantMessageInterface-BuUZFkE5.d.mts → AssistantMessageInterface-Ca0G0vZw.d.mts} +1 -1
- package/dist/{AssistantMessageInterface-CQwh5tEb.d.ts → AssistantMessageInterface-DEZ5AnVl.d.ts} +1 -1
- package/dist/{BlockNoteEditor-V74ICZY3.js → BlockNoteEditor-6UREFUSJ.js} +19 -19
- package/dist/{BlockNoteEditor-V74ICZY3.js.map → BlockNoteEditor-6UREFUSJ.js.map} +1 -1
- package/dist/{BlockNoteEditor-Z3EKI3C6.mjs → BlockNoteEditor-UQOOX3UT.mjs} +4 -4
- package/dist/{auth.interface-CXBF8Mhi.d.mts → auth.interface-r3XKYOrQ.d.mts} +1 -1
- package/dist/{auth.interface-8b601idJ.d.ts → auth.interface-yivTb46w.d.ts} +1 -1
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-3MUBMSDD.js → chunk-6KLR3WJQ.js} +7 -7
- package/dist/{chunk-3MUBMSDD.js.map → chunk-6KLR3WJQ.js.map} +1 -1
- package/dist/{chunk-6LSGIJ5W.js → chunk-HXU2HDRN.js} +1090 -956
- package/dist/chunk-HXU2HDRN.js.map +1 -0
- package/dist/{chunk-UBBTQUFB.mjs → chunk-M7XAKLSP.mjs} +11 -2
- package/dist/{chunk-UBBTQUFB.mjs.map → chunk-M7XAKLSP.mjs.map} +1 -1
- package/dist/{chunk-J3DFHESB.js → chunk-S2PTWBN6.js} +27 -18
- package/dist/chunk-S2PTWBN6.js.map +1 -0
- package/dist/{chunk-BKKVLD42.mjs → chunk-SVEPL5J2.mjs} +514 -380
- package/dist/chunk-SVEPL5J2.mjs.map +1 -0
- package/dist/{chunk-SJRSR3AF.mjs → chunk-WEPQD5MP.mjs} +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +71 -24
- package/dist/components/index.d.ts +71 -24
- package/dist/components/index.js +6 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +5 -3
- package/dist/contexts/index.d.mts +3 -3
- package/dist/contexts/index.d.ts +3 -3
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +10 -8
- package/dist/core/index.d.ts +10 -8
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/features/help/index.js +36 -36
- package/dist/features/help/index.mjs +3 -3
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{notification.interface-DIxR23eS.d.mts → notification.interface-B3kNUTZs.d.mts} +2 -1
- package/dist/{notification.interface-C1T1C2ee.d.ts → notification.interface-LKFmZndR.d.ts} +2 -1
- package/dist/{s3.service-CAL5dhGG.d.mts → s3.service-CihgBy97.d.mts} +2 -2
- package/dist/{s3.service-COLYvcW2.d.ts → s3.service-Cyvfk3qV.d.ts} +2 -2
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/{useSocket-B1fMIr17.d.mts → useSocket-BSjn1Rlb.d.mts} +1 -1
- package/dist/{useSocket-BdJTBXKv.d.ts → useSocket-BsbaGPqE.d.ts} +1 -1
- package/package.json +1 -1
- package/src/components/forms/CommonDeleter.tsx +49 -18
- package/src/components/forms/CommonEditorButtons.tsx +4 -2
- package/src/components/forms/CommonEditorHeader.tsx +20 -14
- package/src/components/forms/CommonEditorTrigger.tsx +18 -4
- package/src/components/forms/DateRangeSelector.tsx +55 -19
- package/src/components/forms/EditorSheet.tsx +36 -9
- package/src/components/forms/FileUploader.tsx +51 -21
- package/src/components/forms/FormDate.tsx +6 -2
- package/src/components/forms/FormDateTime.tsx +11 -2
- package/src/components/forms/FormFieldWrapper.tsx +3 -1
- package/src/components/forms/FormInput.tsx +3 -1
- package/src/components/forms/FormPassword.tsx +10 -1
- package/src/components/forms/FormPlaceAutocomplete.tsx +8 -3
- package/src/components/forms/FormSelect.tsx +3 -0
- package/src/components/forms/FormSlider.tsx +3 -1
- package/src/components/forms/FormTextarea.tsx +15 -1
- package/src/features/notification/data/notification.interface.ts +2 -2
- package/src/features/notification/data/notification.ts +21 -5
- package/src/shadcnui/ui/badge.tsx +15 -0
- package/dist/chunk-6LSGIJ5W.js.map +0 -1
- package/dist/chunk-BKKVLD42.mjs.map +0 -1
- package/dist/chunk-J3DFHESB.js.map +0 -1
- /package/dist/{BlockNoteEditor-Z3EKI3C6.mjs.map → BlockNoteEditor-UQOOX3UT.mjs.map} +0 -0
- /package/dist/{chunk-SJRSR3AF.mjs.map → chunk-WEPQD5MP.mjs.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { F as FeatureInterface } from './feature.interface-CXb1-vNq.mjs';
|
|
2
|
-
import { e as RoleInterface, U as UserInterface, f as UserInput, c as CompanyInterface, b as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-
|
|
2
|
+
import { e as RoleInterface, U as UserInterface, f as UserInput, c as CompanyInterface, b as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-B3kNUTZs.mjs';
|
|
3
3
|
import { A as ApiDataInterface, J as JsonApiHydratedDataInterface } from './ApiDataInterface-BcZeXy5X.mjs';
|
|
4
|
-
import { A as AuthInput, b as AuthInterface } from './auth.interface-
|
|
4
|
+
import { A as AuthInput, b as AuthInterface } from './auth.interface-r3XKYOrQ.mjs';
|
|
5
5
|
import { A as ApiRequestDataTypeInterface } from './ApiRequestDataTypeInterface-CYEcRUrh.mjs';
|
|
6
6
|
|
|
7
7
|
declare enum HttpMethod {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { F as FeatureInterface } from './feature.interface-BO25VLlx.js';
|
|
2
|
-
import { e as RoleInterface, U as UserInterface, f as UserInput, c as CompanyInterface, b as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-
|
|
2
|
+
import { e as RoleInterface, U as UserInterface, f as UserInput, c as CompanyInterface, b as CompanyInput, C as ContentInterface, N as NotificationInterface } from './notification.interface-LKFmZndR.js';
|
|
3
3
|
import { A as ApiDataInterface, J as JsonApiHydratedDataInterface } from './ApiDataInterface-BcZeXy5X.js';
|
|
4
|
-
import { A as AuthInput, b as AuthInterface } from './auth.interface-
|
|
4
|
+
import { A as AuthInput, b as AuthInterface } from './auth.interface-yivTb46w.js';
|
|
5
5
|
import { A as ApiRequestDataTypeInterface } from './ApiRequestDataTypeInterface-CYEcRUrh.js';
|
|
6
6
|
|
|
7
7
|
declare enum HttpMethod {
|
package/dist/server/index.d.mts
CHANGED
|
@@ -2,12 +2,12 @@ import { A as ApiData } from '../ApiData-DPKNfY-9.mjs';
|
|
|
2
2
|
import { M as ModuleWithPermissions, A as Action } from '../types-CQSjy7et.mjs';
|
|
3
3
|
import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.mjs';
|
|
4
4
|
import { A as ApiResponseInterface } from '../ApiResponseInterface-rsXRL_Hn.mjs';
|
|
5
|
-
export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-
|
|
5
|
+
export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-CihgBy97.mjs';
|
|
6
6
|
import 'lucide-react';
|
|
7
7
|
import '../ApiDataInterface-BcZeXy5X.mjs';
|
|
8
8
|
import '../feature.interface-CXb1-vNq.mjs';
|
|
9
|
-
import '../notification.interface-
|
|
10
|
-
import '../auth.interface-
|
|
9
|
+
import '../notification.interface-B3kNUTZs.mjs';
|
|
10
|
+
import '../auth.interface-r3XKYOrQ.mjs';
|
|
11
11
|
|
|
12
12
|
type CacheProfile = "seconds" | "minutes" | "hours" | "days" | "weeks" | "max" | "default";
|
|
13
13
|
/**
|
package/dist/server/index.d.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { A as ApiData } from '../ApiData-DPKNfY-9.js';
|
|
|
2
2
|
import { M as ModuleWithPermissions, A as Action } from '../types-DHOxe8rc.js';
|
|
3
3
|
import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.js';
|
|
4
4
|
import { A as ApiResponseInterface } from '../ApiResponseInterface-CWLvSCvS.js';
|
|
5
|
-
export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-
|
|
5
|
+
export { A as ServerAuthService, C as ServerCompanyService, a as ServerContentService, F as ServerFeatureService, N as ServerNotificationService, P as ServerPushService, R as ServerRoleService, S as ServerS3Service, U as ServerUserService } from '../s3.service-Cyvfk3qV.js';
|
|
6
6
|
import 'lucide-react';
|
|
7
7
|
import '../ApiDataInterface-BcZeXy5X.js';
|
|
8
8
|
import '../feature.interface-BO25VLlx.js';
|
|
9
|
-
import '../notification.interface-
|
|
10
|
-
import '../auth.interface-
|
|
9
|
+
import '../notification.interface-LKFmZndR.js';
|
|
10
|
+
import '../auth.interface-yivTb46w.js';
|
|
11
11
|
|
|
12
12
|
type CacheProfile = "seconds" | "minutes" | "hours" | "days" | "weeks" | "max" | "default";
|
|
13
13
|
/**
|
package/dist/server/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var _chunkIOLU4ASDjs = require('../chunk-IOLU4ASD.js');
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
var
|
|
18
|
+
var _chunkS2PTWBN6js = require('../chunk-S2PTWBN6.js');
|
|
19
19
|
require('../chunk-LXKSUWAV.js');
|
|
20
20
|
require('../chunk-IBS6NI7D.js');
|
|
21
21
|
|
|
@@ -86,7 +86,7 @@ var ServerSession = class {
|
|
|
86
86
|
if (!rawModules) return false;
|
|
87
87
|
const modules = JSON.parse(_pako2.default.ungzip(Buffer.from(rawModules, "base64"), { to: "string" }));
|
|
88
88
|
const selectedModule = modules.find((module) => module.id === params.module.moduleId);
|
|
89
|
-
return
|
|
89
|
+
return _chunkS2PTWBN6js.checkPermissionsFromServer.call(void 0, {
|
|
90
90
|
module: params.module,
|
|
91
91
|
action: params.action,
|
|
92
92
|
data: params.data,
|
|
@@ -296,5 +296,5 @@ _chunk7QVYU63Ejs.__name.call(void 0, ServerJsonApiDelete, "ServerJsonApiDelete")
|
|
|
296
296
|
|
|
297
297
|
|
|
298
298
|
|
|
299
|
-
exports.ServerAuthService =
|
|
299
|
+
exports.ServerAuthService = _chunkS2PTWBN6js.AuthService; exports.ServerCompanyService = _chunkS2PTWBN6js.CompanyService; exports.ServerContentService = _chunkS2PTWBN6js.ContentService; exports.ServerFeatureService = _chunkS2PTWBN6js.FeatureService; exports.ServerJsonApiDelete = ServerJsonApiDelete; exports.ServerJsonApiGet = ServerJsonApiGet; exports.ServerJsonApiPatch = ServerJsonApiPatch; exports.ServerJsonApiPost = ServerJsonApiPost; exports.ServerJsonApiPut = ServerJsonApiPut; exports.ServerNotificationService = _chunkS2PTWBN6js.NotificationService; exports.ServerPushService = _chunkS2PTWBN6js.PushService; exports.ServerRoleService = _chunkS2PTWBN6js.RoleService; exports.ServerS3Service = _chunkS2PTWBN6js.S3Service; exports.ServerSession = ServerSession; exports.ServerUserService = _chunkS2PTWBN6js.UserService; exports.configureServerJsonApi = configureServerJsonApi; exports.getServerApiUrl = getServerApiUrl; exports.getServerAppUrl = getServerAppUrl; exports.getServerToken = _chunkYUO55Q5Ajs.getServerToken; exports.getServerTrackablePages = getServerTrackablePages; exports.invalidateCacheTag = invalidateCacheTag; exports.invalidateCacheTags = invalidateCacheTags; exports.serverRequest = _chunkIOLU4ASDjs.serverRequest;
|
|
300
300
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { LoaderCircleIcon, Trash2Icon } from "lucide-react";
|
|
4
|
-
import { useState } from "react";
|
|
4
|
+
import { ReactNode, useState } from "react";
|
|
5
5
|
import { useI18nRouter, useI18nTranslations } from "../../i18n";
|
|
6
6
|
import {
|
|
7
7
|
AlertDialog,
|
|
@@ -15,15 +15,37 @@ import {
|
|
|
15
15
|
import { errorToast } from "../errors";
|
|
16
16
|
|
|
17
17
|
type CommonDeleterProps = {
|
|
18
|
-
type
|
|
18
|
+
type?: string;
|
|
19
|
+
/** Override the type-derived dialog title. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Override the type-derived dialog subtitle. */
|
|
22
|
+
subtitle?: string;
|
|
23
|
+
/** Override the type-derived dialog body; rendered as-is. */
|
|
24
|
+
description?: ReactNode;
|
|
19
25
|
deleteFunction: () => Promise<void>;
|
|
20
26
|
redirectTo?: string;
|
|
21
27
|
forceShow?: boolean;
|
|
22
28
|
testId?: string;
|
|
29
|
+
/** Custom trigger element replacing the default trash button. */
|
|
30
|
+
trigger?: ReactNode;
|
|
23
31
|
onSuccess?: () => void | Promise<void>;
|
|
32
|
+
/** Fired after a successful delete, in addition to redirect/onSuccess. */
|
|
33
|
+
propagateChanges?: () => void;
|
|
24
34
|
};
|
|
25
35
|
|
|
26
|
-
export function CommonDeleter({
|
|
36
|
+
export function CommonDeleter({
|
|
37
|
+
deleteFunction,
|
|
38
|
+
redirectTo,
|
|
39
|
+
type,
|
|
40
|
+
title,
|
|
41
|
+
subtitle,
|
|
42
|
+
description,
|
|
43
|
+
forceShow,
|
|
44
|
+
testId,
|
|
45
|
+
trigger,
|
|
46
|
+
onSuccess,
|
|
47
|
+
propagateChanges,
|
|
48
|
+
}: CommonDeleterProps) {
|
|
27
49
|
const t = useI18nTranslations();
|
|
28
50
|
const router = useI18nRouter();
|
|
29
51
|
const [open, setOpen] = useState<boolean>(forceShow || false);
|
|
@@ -37,6 +59,7 @@ export function CommonDeleter({ deleteFunction, redirectTo, type, forceShow, tes
|
|
|
37
59
|
setOpen(false);
|
|
38
60
|
if (onSuccess) await onSuccess();
|
|
39
61
|
else if (redirectTo) router.push(redirectTo);
|
|
62
|
+
if (propagateChanges) propagateChanges();
|
|
40
63
|
} catch (error) {
|
|
41
64
|
errorToast({ title: t(`common.errors.delete`), error: error });
|
|
42
65
|
}
|
|
@@ -47,28 +70,36 @@ export function CommonDeleter({ deleteFunction, redirectTo, type, forceShow, tes
|
|
|
47
70
|
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
48
71
|
{forceShow ? null : (
|
|
49
72
|
<AlertDialogTrigger>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
{trigger || (
|
|
74
|
+
<Button
|
|
75
|
+
render={<div />}
|
|
76
|
+
nativeButton={false}
|
|
77
|
+
size="sm"
|
|
78
|
+
variant={"ghost"}
|
|
79
|
+
className="text-muted-foreground hover:text-destructive"
|
|
80
|
+
data-testid={testId}
|
|
81
|
+
>
|
|
82
|
+
<Trash2Icon />
|
|
83
|
+
</Button>
|
|
84
|
+
)}
|
|
60
85
|
</AlertDialogTrigger>
|
|
61
86
|
)}
|
|
62
87
|
<AlertDialogContent className={`flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto`}>
|
|
63
88
|
<AlertDialogHeader>
|
|
64
|
-
<AlertDialogTitle>
|
|
89
|
+
<AlertDialogTitle>
|
|
90
|
+
{title ?? t(`common.delete.title`, { type: t(`entities.${type}`, { count: 1 }) })}
|
|
91
|
+
</AlertDialogTitle>
|
|
65
92
|
<AlertDialogDescription>
|
|
66
|
-
{t(`common.delete.subtitle`, { type: t(`entities.${type}`, { count: 1 }) })}
|
|
93
|
+
{subtitle ?? t(`common.delete.subtitle`, { type: t(`entities.${type}`, { count: 1 }) })}
|
|
67
94
|
</AlertDialogDescription>
|
|
68
95
|
</AlertDialogHeader>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
{description !== undefined ? (
|
|
97
|
+
description
|
|
98
|
+
) : (
|
|
99
|
+
<div className="text-destructive p-4 text-sm">
|
|
100
|
+
{t(`common.delete.description`, { type: t(`entities.${type}`, { count: 1 }) })}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
72
103
|
<div className="flex justify-end">
|
|
73
104
|
<Button
|
|
74
105
|
className="mr-2"
|
|
@@ -9,6 +9,7 @@ type CommonEditorButtonsProps = {
|
|
|
9
9
|
setOpen: (open: boolean) => void;
|
|
10
10
|
hideSubmit?: boolean;
|
|
11
11
|
centerButtons?: ReactNode;
|
|
12
|
+
buttonTitle?: string;
|
|
12
13
|
};
|
|
13
14
|
export function CommonEditorButtons({
|
|
14
15
|
isEdit,
|
|
@@ -17,6 +18,7 @@ export function CommonEditorButtons({
|
|
|
17
18
|
setOpen,
|
|
18
19
|
hideSubmit,
|
|
19
20
|
centerButtons,
|
|
21
|
+
buttonTitle,
|
|
20
22
|
}: CommonEditorButtonsProps) {
|
|
21
23
|
const t = useTranslations();
|
|
22
24
|
|
|
@@ -31,7 +33,7 @@ export function CommonEditorButtons({
|
|
|
31
33
|
|
|
32
34
|
{!hideSubmit ? (
|
|
33
35
|
<Button type="submit" disabled={form.formState.isSubmitting || disabled} data-testid={`modal-button-create`}>
|
|
34
|
-
{isEdit ? t(`ui.buttons.confirm_update`) : t(`ui.buttons.confirm_create`)}
|
|
36
|
+
{buttonTitle ? buttonTitle : isEdit ? t(`ui.buttons.confirm_update`) : t(`ui.buttons.confirm_create`)}
|
|
35
37
|
</Button>
|
|
36
38
|
) : (
|
|
37
39
|
<div />
|
|
@@ -54,7 +56,7 @@ export function CommonEditorButtons({
|
|
|
54
56
|
|
|
55
57
|
{!hideSubmit && (
|
|
56
58
|
<Button type="submit" disabled={form.formState.isSubmitting || disabled} data-testid={`modal-button-create`}>
|
|
57
|
-
{isEdit ? t(`ui.buttons.confirm_update`) : t(`ui.buttons.confirm_create`)}
|
|
59
|
+
{buttonTitle ? buttonTitle : isEdit ? t(`ui.buttons.confirm_update`) : t(`ui.buttons.confirm_create`)}
|
|
58
60
|
</Button>
|
|
59
61
|
)}
|
|
60
62
|
</div>
|
|
@@ -6,29 +6,35 @@ import { DialogDescription, DialogHeader, DialogTitle } from "../../shadcnui";
|
|
|
6
6
|
type CommonEditorHeaderProps = {
|
|
7
7
|
type: string;
|
|
8
8
|
name?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
export function CommonEditorHeader({ type, name }: CommonEditorHeaderProps) {
|
|
13
|
+
export function CommonEditorHeader({ type, name, title, description }: CommonEditorHeaderProps) {
|
|
12
14
|
const t = useTranslations();
|
|
13
15
|
|
|
14
16
|
return (
|
|
15
17
|
<DialogHeader>
|
|
16
18
|
<DialogTitle>
|
|
17
|
-
{
|
|
18
|
-
?
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
{title
|
|
20
|
+
? title
|
|
21
|
+
: name
|
|
22
|
+
? t(`common.edit.update.title`, {
|
|
23
|
+
type: type,
|
|
24
|
+
})
|
|
25
|
+
: t(`common.edit.create.title`, { type: type })}
|
|
22
26
|
</DialogTitle>
|
|
23
27
|
<DialogDescription>
|
|
24
|
-
{
|
|
25
|
-
?
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
{description
|
|
29
|
+
? description
|
|
30
|
+
: name
|
|
31
|
+
? t(`common.edit.update.description`, {
|
|
32
|
+
type: type,
|
|
33
|
+
name: name,
|
|
34
|
+
})
|
|
35
|
+
: t(`common.edit.create.description`, {
|
|
36
|
+
type: type,
|
|
37
|
+
})}
|
|
32
38
|
</DialogDescription>
|
|
33
39
|
</DialogHeader>
|
|
34
40
|
);
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { PencilIcon } from "lucide-react";
|
|
3
|
+
import { PencilIcon, PlusCircleIcon } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
+
import React from "react";
|
|
6
7
|
import { Button, DialogTrigger } from "../../shadcnui";
|
|
7
8
|
|
|
8
|
-
type CommonEditorTriggerProps = { isEdit: boolean; edit?: string; create?: string; testId?: string };
|
|
9
|
+
type CommonEditorTriggerProps = { isEdit: boolean; edit?: string; create?: string; testId?: string; title?: string };
|
|
9
10
|
|
|
10
|
-
export function CommonEditorTrigger({ isEdit, edit: _edit, create, testId }: CommonEditorTriggerProps) {
|
|
11
|
+
export function CommonEditorTrigger({ isEdit, edit: _edit, create, testId, title }: CommonEditorTriggerProps) {
|
|
11
12
|
const t = useTranslations();
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
15
|
<DialogTrigger>
|
|
15
|
-
{
|
|
16
|
+
{title ? (
|
|
17
|
+
<Button render={<div />} nativeButton={false} size="sm" variant={`outline`} data-testid={testId}>
|
|
18
|
+
{title}
|
|
19
|
+
</Button>
|
|
20
|
+
) : isEdit ? (
|
|
16
21
|
<Button
|
|
17
22
|
render={<div />}
|
|
18
23
|
nativeButton={false}
|
|
@@ -31,3 +36,12 @@ export function CommonEditorTrigger({ isEdit, edit: _edit, create, testId }: Com
|
|
|
31
36
|
</DialogTrigger>
|
|
32
37
|
);
|
|
33
38
|
}
|
|
39
|
+
|
|
40
|
+
export const CommonAddTrigger = React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<typeof Button>>(
|
|
41
|
+
(props, ref) => (
|
|
42
|
+
<Button ref={ref} variant="ghost" size="sm" className="text-muted-foreground" {...props}>
|
|
43
|
+
<PlusCircleIcon />
|
|
44
|
+
</Button>
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
CommonAddTrigger.displayName = "CommonAddTrigger";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { format } from "date-fns";
|
|
4
4
|
import { CalendarIcon } from "lucide-react";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
5
6
|
import { useEffect, useState } from "react";
|
|
6
7
|
import { DateRange } from "react-day-picker";
|
|
7
8
|
import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from "../../shadcnui";
|
|
@@ -14,6 +15,7 @@ type DateRangeSelectorProps = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviousMonth }: DateRangeSelectorProps) {
|
|
18
|
+
const t = useTranslations();
|
|
17
19
|
const [date, setDate] = useState<DateRange | undefined>(
|
|
18
20
|
avoidSettingDates
|
|
19
21
|
? undefined
|
|
@@ -24,6 +26,12 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
|
|
|
24
26
|
);
|
|
25
27
|
|
|
26
28
|
const [open, setOpen] = useState(false);
|
|
29
|
+
const [mounted, setMounted] = useState(false);
|
|
30
|
+
|
|
31
|
+
// Prevent hydration mismatch by only rendering Popover after mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setMounted(true);
|
|
34
|
+
}, []);
|
|
27
35
|
|
|
28
36
|
const [prevRange, setPrevRange] = useState<DateRange | undefined>(date);
|
|
29
37
|
useEffect(() => {
|
|
@@ -53,28 +61,56 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
|
|
|
53
61
|
}
|
|
54
62
|
};
|
|
55
63
|
|
|
64
|
+
// Show placeholder button during SSR to prevent hydration mismatch
|
|
65
|
+
if (!mounted) {
|
|
66
|
+
return (
|
|
67
|
+
<div className={cn("grid gap-2")}>
|
|
68
|
+
<Button
|
|
69
|
+
id="date"
|
|
70
|
+
variant={"outline"}
|
|
71
|
+
className={cn("w-[300px] justify-start text-left font-normal", !date && "text-muted-foreground")}
|
|
72
|
+
>
|
|
73
|
+
<CalendarIcon />
|
|
74
|
+
{date?.from ? (
|
|
75
|
+
date.to ? (
|
|
76
|
+
<>
|
|
77
|
+
{format(date.from, "LLL dd, y")} - {format(date.to, "LLL dd, y")}
|
|
78
|
+
</>
|
|
79
|
+
) : (
|
|
80
|
+
format(date.from, "LLL dd, y")
|
|
81
|
+
)
|
|
82
|
+
) : (
|
|
83
|
+
<span>{t("ui.labels.pick_a_date")}</span>
|
|
84
|
+
)}
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
56
90
|
return (
|
|
57
91
|
<div className={cn("grid gap-2")}>
|
|
58
92
|
<Popover open={open} onOpenChange={setOpen}>
|
|
59
|
-
<PopoverTrigger
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
format(date.from, "LLL dd, y")
|
|
73
|
-
|
|
93
|
+
<PopoverTrigger
|
|
94
|
+
render={
|
|
95
|
+
<Button
|
|
96
|
+
id="date"
|
|
97
|
+
variant={"outline"}
|
|
98
|
+
className={cn("w-[300px] justify-start text-left font-normal", !date && "text-muted-foreground")}
|
|
99
|
+
/>
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<CalendarIcon />
|
|
103
|
+
{date?.from ? (
|
|
104
|
+
date.to ? (
|
|
105
|
+
<>
|
|
106
|
+
{format(date.from, "LLL dd, y")} - {format(date.to, "LLL dd, y")}
|
|
107
|
+
</>
|
|
74
108
|
) : (
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
109
|
+
format(date.from, "LLL dd, y")
|
|
110
|
+
)
|
|
111
|
+
) : (
|
|
112
|
+
<span>{t("ui.labels.pick_a_date")}</span>
|
|
113
|
+
)}
|
|
78
114
|
</PopoverTrigger>
|
|
79
115
|
<PopoverContent className="w-auto p-0" align="start">
|
|
80
116
|
<div className="flex flex-col gap-2 p-2">
|
|
@@ -100,7 +136,7 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
|
|
|
100
136
|
className="cursor-pointer"
|
|
101
137
|
disabled={!date}
|
|
102
138
|
>
|
|
103
|
-
|
|
139
|
+
{t("ui.buttons.clear")}
|
|
104
140
|
</Button>
|
|
105
141
|
</div>
|
|
106
142
|
</PopoverContent>
|
|
@@ -62,6 +62,17 @@ export type EditorSheetProps<T extends FieldValues> = {
|
|
|
62
62
|
dialogOpen?: boolean;
|
|
63
63
|
onDialogOpenChange?: (open: boolean) => void;
|
|
64
64
|
|
|
65
|
+
/** Render a fully custom footer instead of the default CommonEditorButtons.
|
|
66
|
+
* `setOpen` is the dirty-checked open handler (shows the discard dialog);
|
|
67
|
+
* `closeWithoutConfirm` is the raw setter that bypasses the discard dialog
|
|
68
|
+
* (use after a successful submit when no confirmation is needed). */
|
|
69
|
+
renderFooter?: (props: {
|
|
70
|
+
form: UseFormReturn<T>;
|
|
71
|
+
isEdit: boolean;
|
|
72
|
+
setOpen: (open: boolean) => void;
|
|
73
|
+
closeWithoutConfirm: (open: boolean) => void;
|
|
74
|
+
}) => ReactNode;
|
|
75
|
+
|
|
65
76
|
children: ReactNode;
|
|
66
77
|
};
|
|
67
78
|
|
|
@@ -90,6 +101,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
90
101
|
onClose,
|
|
91
102
|
dialogOpen,
|
|
92
103
|
onDialogOpenChange,
|
|
104
|
+
renderFooter,
|
|
93
105
|
children,
|
|
94
106
|
}: EditorSheetProps<T>) {
|
|
95
107
|
const t = useTranslations();
|
|
@@ -147,6 +159,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
147
159
|
<>
|
|
148
160
|
<Sheet open={open} onOpenChange={handleOpenChange}>
|
|
149
161
|
{dialogOpen === undefined &&
|
|
162
|
+
forceShow === undefined &&
|
|
150
163
|
(trigger ? (
|
|
151
164
|
<SheetTrigger>{trigger}</SheetTrigger>
|
|
152
165
|
) : (
|
|
@@ -184,17 +197,31 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
184
197
|
</SheetDescription>
|
|
185
198
|
</SheetHeader>
|
|
186
199
|
<Form {...form}>
|
|
187
|
-
<form
|
|
200
|
+
<form
|
|
201
|
+
onSubmit={(e) => {
|
|
202
|
+
// The Sheet content is portaled, but React synthetic events still
|
|
203
|
+
// bubble up the React tree — so without stopPropagation an inner
|
|
204
|
+
// EditorSheet's submit also triggers the outer form's submit
|
|
205
|
+
// (e.g. a create dialog opened from within another editor).
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
return form.handleSubmit(wrappedOnSubmit)(e);
|
|
208
|
+
}}
|
|
209
|
+
className="flex min-h-0 flex-1 flex-col"
|
|
210
|
+
>
|
|
188
211
|
<div className="flex-1 overflow-y-auto px-6 py-4">{children}</div>
|
|
189
212
|
<SheetFooter className="shrink-0 border-t px-6 py-4">
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
{renderFooter ? (
|
|
214
|
+
renderFooter({ form, isEdit, setOpen: handleOpenChange, closeWithoutConfirm: setOpen })
|
|
215
|
+
) : (
|
|
216
|
+
<CommonEditorButtons
|
|
217
|
+
form={form}
|
|
218
|
+
setOpen={handleOpenChange}
|
|
219
|
+
isEdit={isEdit}
|
|
220
|
+
disabled={disabled}
|
|
221
|
+
hideSubmit={hideSubmit}
|
|
222
|
+
centerButtons={centerButtons}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
198
225
|
</SheetFooter>
|
|
199
226
|
</form>
|
|
200
227
|
</Form>
|
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
useCallback,
|
|
11
11
|
useContext,
|
|
12
12
|
useEffect,
|
|
13
|
+
useMemo,
|
|
13
14
|
useRef,
|
|
14
15
|
useState,
|
|
15
16
|
} from "react";
|
|
16
|
-
import { DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
|
|
17
|
+
import { Accept, DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
|
|
17
18
|
import { showError } from "../../utils/toast";
|
|
18
|
-
import { buttonVariants, Input } from "../../shadcnui";
|
|
19
|
+
import { buttonVariants, Input, Tooltip, TooltipContent, TooltipTrigger } from "../../shadcnui";
|
|
19
20
|
import { cn } from "../../utils";
|
|
20
21
|
|
|
21
22
|
export type { DropzoneOptions } from "react-dropzone";
|
|
@@ -31,6 +32,7 @@ type FileUploaderContextType = {
|
|
|
31
32
|
setActiveIndex: Dispatch<SetStateAction<number>>;
|
|
32
33
|
orientation: "horizontal" | "vertical";
|
|
33
34
|
direction: DirectionOptions;
|
|
35
|
+
accept?: Accept;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
|
|
@@ -195,6 +197,7 @@ export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React
|
|
|
195
197
|
setActiveIndex,
|
|
196
198
|
orientation,
|
|
197
199
|
direction,
|
|
200
|
+
accept: dropzoneOptions.accept,
|
|
198
201
|
}}
|
|
199
202
|
>
|
|
200
203
|
<div
|
|
@@ -283,35 +286,62 @@ FileUploaderItem.displayName = "FileUploaderItem";
|
|
|
283
286
|
|
|
284
287
|
export const FileInput = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
285
288
|
({ className, children, ...props }, ref) => {
|
|
286
|
-
const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();
|
|
289
|
+
const { dropzoneState, isFileTooBig, isLOF, accept } = useFileUpload();
|
|
290
|
+
const t = useTranslations();
|
|
287
291
|
const rootProps = isLOF ? {} : dropzoneState.getRootProps();
|
|
288
292
|
// Get isDragActive from the context for FileInput as well, to ensure it can react if needed, or to simplify its own styling.
|
|
289
293
|
const { isDragActive: parentIsDragActive } = dropzoneState;
|
|
290
294
|
|
|
295
|
+
const acceptedLabels = useMemo(() => {
|
|
296
|
+
if (!accept) return null;
|
|
297
|
+
const extensions = new Set<string>();
|
|
298
|
+
let hasWildcardImages = false;
|
|
299
|
+
for (const [mime, exts] of Object.entries(accept)) {
|
|
300
|
+
if (mime === "image/*") hasWildcardImages = true;
|
|
301
|
+
for (const ext of exts) extensions.add(ext);
|
|
302
|
+
}
|
|
303
|
+
const labels = Array.from(extensions).sort();
|
|
304
|
+
if (hasWildcardImages) labels.push(t("ui.labels.images"));
|
|
305
|
+
return labels.length > 0 ? labels : null;
|
|
306
|
+
}, [accept, t]);
|
|
307
|
+
|
|
308
|
+
const dropArea = (
|
|
309
|
+
<div
|
|
310
|
+
className={cn(
|
|
311
|
+
"w-full rounded-lg duration-300 ease-in-out",
|
|
312
|
+
// Simpler border logic: if parent is drag-active, it controls the border.
|
|
313
|
+
// Otherwise, FileInput can show its own accept/reject/default border.
|
|
314
|
+
{
|
|
315
|
+
"border-green-500": dropzoneState.isDragAccept && !parentIsDragActive,
|
|
316
|
+
"border-red-500": (dropzoneState.isDragReject || isFileTooBig) && !parentIsDragActive,
|
|
317
|
+
"border-gray-300":
|
|
318
|
+
!dropzoneState.isDragAccept && !dropzoneState.isDragReject && !isFileTooBig && !parentIsDragActive,
|
|
319
|
+
},
|
|
320
|
+
// className from props should be last to allow overrides if necessary
|
|
321
|
+
className,
|
|
322
|
+
)}
|
|
323
|
+
{...rootProps}
|
|
324
|
+
>
|
|
325
|
+
{children}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
|
|
291
329
|
return (
|
|
292
330
|
<div
|
|
293
331
|
ref={ref}
|
|
294
332
|
{...props}
|
|
295
333
|
className={cn(`relative w-full ${isLOF ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`, className)}
|
|
296
334
|
>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
},
|
|
308
|
-
// className from props should be last to allow overrides if necessary
|
|
309
|
-
className,
|
|
310
|
-
)}
|
|
311
|
-
{...rootProps}
|
|
312
|
-
>
|
|
313
|
-
{children}
|
|
314
|
-
</div>
|
|
335
|
+
{acceptedLabels ? (
|
|
336
|
+
<Tooltip>
|
|
337
|
+
<TooltipTrigger render={dropArea} />
|
|
338
|
+
<TooltipContent>
|
|
339
|
+
{t("ui.labels.accepted_file_types")}: {acceptedLabels.join(", ")}
|
|
340
|
+
</TooltipContent>
|
|
341
|
+
</Tooltip>
|
|
342
|
+
) : (
|
|
343
|
+
dropArea
|
|
344
|
+
)}
|
|
315
345
|
<Input
|
|
316
346
|
ref={dropzoneState.inputRef}
|
|
317
347
|
disabled={isLOF}
|