@carlonicora/nextjs-jsonapi 1.74.0 → 1.75.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/{BlockNoteEditor-KJZ7FGBA.mjs → BlockNoteEditor-NJMTHPO4.mjs} +2 -2
- package/dist/{BlockNoteEditor-A37P3FA7.js → BlockNoteEditor-SLT4VOLL.js} +6 -6
- package/dist/{BlockNoteEditor-A37P3FA7.js.map → BlockNoteEditor-SLT4VOLL.js.map} +1 -1
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-XUTMY6K5.js → chunk-DTE6RZXF.js} +606 -526
- package/dist/chunk-DTE6RZXF.js.map +1 -0
- package/dist/{chunk-ZNODEBMI.mjs → chunk-Q7JKB777.mjs} +2380 -2300
- package/dist/chunk-Q7JKB777.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +29 -3
- package/dist/components/index.d.ts +29 -3
- package/dist/components/index.js +4 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +14 -120
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-web-module/templates/components/multi-selector.template.ts +14 -120
- package/src/components/forms/EntityMultiSelector.tsx +325 -0
- package/src/components/forms/index.ts +1 -0
- package/src/features/how-to/components/forms/HowToMultiSelector.tsx +14 -120
- package/src/features/user/components/forms/UserMultiSelect.tsx +34 -181
- package/dist/chunk-XUTMY6K5.js.map +0 -1
- package/dist/chunk-ZNODEBMI.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-KJZ7FGBA.mjs.map → BlockNoteEditor-NJMTHPO4.mjs.map} +0 -0
|
@@ -1,41 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Loader2 } from "lucide-react";
|
|
4
3
|
import { useTranslations } from "next-intl";
|
|
5
|
-
import {
|
|
6
|
-
import { useWatch } from "react-hook-form";
|
|
7
|
-
import { FormFieldWrapper } from "../../../../components/forms";
|
|
4
|
+
import { EntityMultiSelector } from "../../../../components/forms/EntityMultiSelector";
|
|
8
5
|
import { Modules } from "../../../../core";
|
|
9
|
-
import {
|
|
10
|
-
import { Avatar, AvatarFallback, AvatarImage, MultipleSelector } from "../../../../shadcnui";
|
|
11
|
-
import { Option } from "../../../../shadcnui";
|
|
6
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../../../../shadcnui";
|
|
12
7
|
import { useCurrentUserContext } from "../../contexts";
|
|
13
8
|
import { UserInterface } from "../../data";
|
|
14
9
|
import { UserService } from "../../data/user.service";
|
|
15
10
|
|
|
16
|
-
// Type for user objects in the form
|
|
17
|
-
type UserSelectType = {
|
|
18
|
-
id: string;
|
|
19
|
-
name: string;
|
|
20
|
-
avatar?: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type UserMultiSelectProps = {
|
|
24
|
-
id: string;
|
|
25
|
-
form: any;
|
|
26
|
-
currentUser?: UserInterface;
|
|
27
|
-
label?: string;
|
|
28
|
-
placeholder?: string;
|
|
29
|
-
onChange?: (users?: UserInterface[]) => void;
|
|
30
|
-
maxCount?: number;
|
|
31
|
-
isRequired?: boolean;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type UserOption = Option & {
|
|
35
|
-
userData?: UserInterface;
|
|
36
|
-
avatar?: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
11
|
function UserAvatarIcon({ className, url, name }: { className?: string; url?: string; name?: string }) {
|
|
40
12
|
return (
|
|
41
13
|
<Avatar className={`${className || "h-4 w-4"}`}>
|
|
@@ -52,6 +24,17 @@ function UserAvatarIcon({ className, url, name }: { className?: string; url?: st
|
|
|
52
24
|
);
|
|
53
25
|
}
|
|
54
26
|
|
|
27
|
+
type UserMultiSelectProps = {
|
|
28
|
+
id: string;
|
|
29
|
+
form: any;
|
|
30
|
+
currentUser?: UserInterface;
|
|
31
|
+
label?: string;
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
onChange?: (users?: UserInterface[]) => void;
|
|
34
|
+
maxCount?: number;
|
|
35
|
+
isRequired?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
55
38
|
export function UserMultiSelect({
|
|
56
39
|
id,
|
|
57
40
|
form,
|
|
@@ -59,162 +42,32 @@ export function UserMultiSelect({
|
|
|
59
42
|
label,
|
|
60
43
|
placeholder,
|
|
61
44
|
onChange,
|
|
62
|
-
maxCount = 3,
|
|
63
45
|
isRequired = false,
|
|
64
46
|
}: UserMultiSelectProps) {
|
|
65
47
|
const t = useTranslations();
|
|
66
48
|
const { company } = useCurrentUserContext<UserInterface>();
|
|
67
49
|
|
|
68
|
-
const searchTermRef = useRef<string>("");
|
|
69
|
-
const [searchTerm, _setSearchTerm] = useState<string>("");
|
|
70
|
-
const [isSearching, setIsSearching] = useState<boolean>(false);
|
|
71
|
-
const [userOptions, setUserOptions] = useState<UserOption[]>([]);
|
|
72
|
-
|
|
73
|
-
// Get the current selected users from the form
|
|
74
|
-
const selectedUsers: UserSelectType[] = useWatch({ control: form.control, name: id }) || [];
|
|
75
|
-
|
|
76
|
-
const data: DataListRetriever<UserInterface> = useDataListRetriever({
|
|
77
|
-
ready: !!company,
|
|
78
|
-
retriever: (params) => {
|
|
79
|
-
return UserService.findAllUsers(params);
|
|
80
|
-
},
|
|
81
|
-
retrieverParams: { companyId: company?.id },
|
|
82
|
-
module: Modules.User,
|
|
83
|
-
}) as DataListRetriever<UserInterface>;
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (company) data.setReady(true);
|
|
87
|
-
}, [company]);
|
|
88
|
-
|
|
89
|
-
const search = useCallback(
|
|
90
|
-
async (searchedTerm: string) => {
|
|
91
|
-
try {
|
|
92
|
-
if (searchedTerm === searchTermRef.current) return;
|
|
93
|
-
setIsSearching(true);
|
|
94
|
-
searchTermRef.current = searchedTerm;
|
|
95
|
-
await data.search(searchedTerm);
|
|
96
|
-
} finally {
|
|
97
|
-
setIsSearching(false);
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
[searchTermRef, data],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const updateSearchTerm = useDebounce(search, 500);
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
setIsSearching(true);
|
|
107
|
-
updateSearchTerm(searchTerm);
|
|
108
|
-
}, [updateSearchTerm, searchTerm]);
|
|
109
|
-
|
|
110
|
-
// Update userOptions when data changes or when initial selected users are available
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (data.data && data.data.length > 0) {
|
|
113
|
-
const users = data.data as UserInterface[];
|
|
114
|
-
const filteredUsers = users.filter((user) => user.id !== currentUser?.id);
|
|
115
|
-
|
|
116
|
-
const options: UserOption[] = filteredUsers.map((user) => ({
|
|
117
|
-
label: user.name,
|
|
118
|
-
value: user.id,
|
|
119
|
-
userData: user,
|
|
120
|
-
avatar: user.avatar,
|
|
121
|
-
}));
|
|
122
|
-
|
|
123
|
-
// Add options for any already selected users that aren't in search results
|
|
124
|
-
const existingOptionIds = new Set(options.map((option) => option.value));
|
|
125
|
-
const missingOptions: UserOption[] = selectedUsers
|
|
126
|
-
.filter((user) => !existingOptionIds.has(user.id))
|
|
127
|
-
.map((user) => ({
|
|
128
|
-
label: user.name,
|
|
129
|
-
value: user.id,
|
|
130
|
-
userData: user as unknown as UserInterface,
|
|
131
|
-
avatar: user.avatar,
|
|
132
|
-
}));
|
|
133
|
-
|
|
134
|
-
setUserOptions([...options, ...missingOptions]);
|
|
135
|
-
}
|
|
136
|
-
}, [data.data, currentUser, selectedUsers]);
|
|
137
|
-
|
|
138
|
-
// Convert selected users to Option[] format
|
|
139
|
-
const selectedOptions = useMemo(() => {
|
|
140
|
-
return selectedUsers.map((user) => ({
|
|
141
|
-
value: user.id,
|
|
142
|
-
label: user.name,
|
|
143
|
-
}));
|
|
144
|
-
}, [selectedUsers]);
|
|
145
|
-
|
|
146
|
-
const handleChange = (options: Option[]) => {
|
|
147
|
-
// Convert to form format
|
|
148
|
-
const formValues = options.map((option) => {
|
|
149
|
-
const userOption = userOptions.find((opt) => opt.value === option.value);
|
|
150
|
-
return {
|
|
151
|
-
id: option.value,
|
|
152
|
-
name: option.label,
|
|
153
|
-
avatar: userOption?.avatar,
|
|
154
|
-
};
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
form.setValue(id, formValues, { shouldDirty: true, shouldTouch: true });
|
|
158
|
-
|
|
159
|
-
if (onChange) {
|
|
160
|
-
// Get full user data for onChange callback
|
|
161
|
-
const fullUsers = options
|
|
162
|
-
.map((option) => {
|
|
163
|
-
const userOption = userOptions.find((opt) => opt.value === option.value);
|
|
164
|
-
return userOption?.userData;
|
|
165
|
-
})
|
|
166
|
-
.filter(Boolean) as UserInterface[];
|
|
167
|
-
onChange(fullUsers);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// Custom render function for dropdown options (with avatar)
|
|
172
|
-
const renderOption = (option: Option) => {
|
|
173
|
-
const userOption = option as UserOption;
|
|
174
|
-
return (
|
|
175
|
-
<span className="flex items-center gap-2">
|
|
176
|
-
<UserAvatarIcon url={userOption.avatar} name={option.label} />
|
|
177
|
-
{option.label}
|
|
178
|
-
</span>
|
|
179
|
-
);
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// Search handler
|
|
183
|
-
const handleSearchSync = (search: string): Option[] => {
|
|
184
|
-
_setSearchTerm(search);
|
|
185
|
-
return userOptions;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
50
|
return (
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
emptyIndicator={
|
|
211
|
-
<span className="text-muted-foreground">
|
|
212
|
-
{t("ui.search.no_results", { type: t("entities.users", { count: 2 }) })}
|
|
213
|
-
</span>
|
|
214
|
-
}
|
|
215
|
-
/>
|
|
216
|
-
)}
|
|
217
|
-
</FormFieldWrapper>
|
|
218
|
-
</div>
|
|
51
|
+
<EntityMultiSelector<UserInterface>
|
|
52
|
+
id={id}
|
|
53
|
+
form={form}
|
|
54
|
+
label={label}
|
|
55
|
+
placeholder={placeholder || t("ui.search.button")}
|
|
56
|
+
emptyText={t("ui.search.no_results", { type: t("entities.users", { count: 2 }) })}
|
|
57
|
+
isRequired={isRequired}
|
|
58
|
+
retriever={(params) => UserService.findAllUsers(params)}
|
|
59
|
+
retrieverParams={{ companyId: company?.id }}
|
|
60
|
+
module={Modules.User}
|
|
61
|
+
getLabel={(user) => user.name}
|
|
62
|
+
toFormValue={(user) => ({ id: user.id, name: user.name, avatar: user.avatar })}
|
|
63
|
+
excludeId={currentUser?.id}
|
|
64
|
+
onChange={onChange}
|
|
65
|
+
renderOption={(user) => (
|
|
66
|
+
<span className="flex items-center gap-2">
|
|
67
|
+
<UserAvatarIcon url={user.avatar} name={user.name} />
|
|
68
|
+
{user.name}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
/>
|
|
219
72
|
);
|
|
220
73
|
}
|