@appcorp/fusion-storybook 0.4.69 → 0.5.3

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.
@@ -32,7 +32,7 @@ import { workspaceUserFormValidation } from "./validate";
32
32
  import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
33
33
  import { useTranslations } from "next-intl";
34
34
  import { getCachedWorkspaceSync } from "../workspace/cache";
35
- import { Eye, Filter, Plus } from "lucide-react";
35
+ import { Eye, Filter, Pencil, Plus, Trash2 } from "lucide-react";
36
36
  // ============================================================================
37
37
  // 1.1 DRAWER TYPES
38
38
  // ============================================================================
@@ -69,6 +69,7 @@ const workspaceUserConfig = {
69
69
  workspaceId: "",
70
70
  // Relations (populated from byId fetch)
71
71
  role: undefined,
72
+ user: undefined,
72
73
  workspace: undefined,
73
74
  },
74
75
  drawerTypes: DRAWER_TYPES,
@@ -108,8 +109,16 @@ export const useWorkspaceUserModule = () => {
108
109
  id: state.id,
109
110
  roleId: state.roleId,
110
111
  userId: state.userId,
111
- workspaceId: workspace === null || workspace === void 0 ? void 0 : workspace.id,
112
- }), [state.enabled, state.id, state.roleId, state.userId, workspace === null || workspace === void 0 ? void 0 : workspace.id]);
112
+ // The form field is read-only; the cached workspace is the source of truth.
113
+ workspaceId: state.workspaceId || (workspace === null || workspace === void 0 ? void 0 : workspace.id),
114
+ }), [
115
+ state.enabled,
116
+ state.id,
117
+ state.roleId,
118
+ state.userId,
119
+ state.workspaceId,
120
+ workspace === null || workspace === void 0 ? void 0 : workspace.id,
121
+ ]);
113
122
  const byIdParams = useMemo(() => ({ id: state.id }), [state.id]);
114
123
  const deleteParams = useMemo(() => ({ id: state.id }), [state.id]);
115
124
  // ============================================================================
@@ -150,17 +159,17 @@ export const useWorkspaceUserModule = () => {
150
159
  payload: {
151
160
  form: {
152
161
  enabled: true,
153
- filterEnabled: undefined,
154
162
  id: "",
155
163
  roleId: null,
156
164
  userId: "",
157
- workspaceId: "",
165
+ workspaceId: (workspace === null || workspace === void 0 ? void 0 : workspace.id) || "",
158
166
  role: undefined,
167
+ user: undefined,
159
168
  workspace: undefined,
160
169
  },
161
170
  },
162
171
  });
163
- }, [dispatch]);
172
+ }, [dispatch, workspace === null || workspace === void 0 ? void 0 : workspace.id]);
164
173
  // ============================================================================
165
174
  // 1.4.4 API CALLBACKS
166
175
  // ============================================================================
@@ -181,7 +190,6 @@ export const useWorkspaceUserModule = () => {
181
190
  }
182
191
  }, [dispatch, showToast, t]);
183
192
  const updateCallback = useCallback(({ data, error }) => {
184
- var _a;
185
193
  const isCreated = isCreatedOrUpdated(data);
186
194
  if (error) {
187
195
  showToast(isCreated
@@ -191,7 +199,6 @@ export const useWorkspaceUserModule = () => {
191
199
  }
192
200
  if (data) {
193
201
  showToast(t("messagesWorkspaceUserSaved"), TOAST_VARIANT.SUCCESS);
194
- (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
195
202
  resetFormAndCloseDrawer();
196
203
  }
197
204
  }, [resetFormAndCloseDrawer, showToast, t]);
@@ -203,25 +210,25 @@ export const useWorkspaceUserModule = () => {
203
210
  if (data) {
204
211
  dispatch({
205
212
  type: WORKSPACE_USER_ACTION_TYPES.SET_FORM_DATA,
206
- payload: { form: Object.assign(Object.assign({}, data), { filterEnabled: undefined }) },
213
+ payload: { form: data },
207
214
  });
208
215
  }
209
216
  }, [dispatch, showToast, t]);
210
217
  const deleteCallback = useCallback(({ data, error }) => {
211
- var _a;
212
218
  if (error) {
213
219
  showToast(t("messagesWorkspaceUserDeleteFailed"), TOAST_VARIANT.ERROR);
214
220
  return;
215
221
  }
216
222
  if (data) {
217
223
  showToast(t("messagesWorkspaceUserDeleted"), TOAST_VARIANT.SUCCESS);
218
- (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
224
+ resetRecordFormState();
225
+ resetFormAndCloseDrawer();
219
226
  }
220
- }, [showToast, t]);
227
+ }, [resetFormAndCloseDrawer, resetRecordFormState, showToast, t]);
221
228
  // ============================================================================
222
229
  // 1.4.5 API HOOKS
223
230
  // ============================================================================
224
- const { listFetchNow, listLoading, updateFetchNow, updateLoading, deleteFetchNow, deleteLoading, byIdLoading, } = useModuleEntityV2({
231
+ const { listFetchNow, listLoading, updateFetchNow, updateLoading, deleteFetchNow, deleteLoading, byIdFetchNow, byIdLoading, } = useModuleEntityV2({
225
232
  byIdCallback,
226
233
  byIdParams,
227
234
  deleteCallback,
@@ -248,9 +255,18 @@ export const useWorkspaceUserModule = () => {
248
255
  const handleChange = useCallback((field, value) => {
249
256
  dispatch({
250
257
  type: WORKSPACE_USER_ACTION_TYPES.SET_INPUT_FIELD,
251
- payload: { field, value },
258
+ payload: { key: field, value },
252
259
  });
253
- }, [dispatch]);
260
+ // A retried field must not keep blocking the Save button.
261
+ if (state.errors[field]) {
262
+ const nextErrors = Object.assign({}, state.errors);
263
+ delete nextErrors[field];
264
+ dispatch({
265
+ type: WORKSPACE_USER_ACTION_TYPES.SET_ERRORS,
266
+ payload: { errors: nextErrors },
267
+ });
268
+ }
269
+ }, [dispatch, state.errors]);
254
270
  const handleFilters = useCallback(() => {
255
271
  dispatch({
256
272
  type: WORKSPACE_USER_ACTION_TYPES.SET_DRAWER,
@@ -260,15 +276,16 @@ export const useWorkspaceUserModule = () => {
260
276
  const handleMoreActions = useCallback(() => {
261
277
  dispatch({
262
278
  type: WORKSPACE_USER_ACTION_TYPES.SET_DRAWER,
263
- payload: { drawer: WORKSPACE_USER_DRAWER.FORM_DRAWER },
279
+ payload: { drawer: WORKSPACE_USER_DRAWER.MORE_ACTIONS_DRAWER },
264
280
  });
265
281
  }, [dispatch]);
266
- const handleView = useCallback(() => {
282
+ const handleView = useCallback((row) => {
283
+ byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
267
284
  dispatch({
268
285
  type: WORKSPACE_USER_ACTION_TYPES.SET_DRAWER,
269
286
  payload: { drawer: WORKSPACE_USER_DRAWER.VIEW_DRAWER },
270
287
  });
271
- }, [dispatch]);
288
+ }, [byIdFetchNow, dispatch]);
272
289
  const handleSearch = useCallback((query) => {
273
290
  dispatch({
274
291
  type: WORKSPACE_USER_ACTION_TYPES.SET_SEARCH_QUERY,
@@ -299,14 +316,49 @@ export const useWorkspaceUserModule = () => {
299
316
  });
300
317
  }, [dispatch]);
301
318
  const handleDelete = useCallback((row) => {
302
- if (row === null || row === void 0 ? void 0 : row.id) {
303
- deleteFetchNow === null || deleteFetchNow === void 0 ? void 0 : deleteFetchNow(undefined, {
304
- body: JSON.stringify({ id: row.id }),
305
- });
306
- }
307
- }, [deleteFetchNow]);
308
- const handleCreate = useCallback(() => { }, []);
309
- const handleEdit = useCallback(() => { }, []);
319
+ if (!(row === null || row === void 0 ? void 0 : row.id))
320
+ return;
321
+ if (!confirm(t("messagesDeleteConfirmation")))
322
+ return;
323
+ deleteFetchNow === null || deleteFetchNow === void 0 ? void 0 : deleteFetchNow(undefined, {
324
+ body: JSON.stringify({ id: row.id }),
325
+ });
326
+ }, [deleteFetchNow, t]);
327
+ const handleCreate = useCallback(() => {
328
+ resetRecordFormState();
329
+ dispatch({
330
+ type: WORKSPACE_USER_ACTION_TYPES.SET_DRAWER,
331
+ payload: { drawer: WORKSPACE_USER_DRAWER.FORM_DRAWER },
332
+ });
333
+ }, [dispatch, resetRecordFormState]);
334
+ const handleEdit = useCallback((row) => {
335
+ var _a, _b, _c, _d, _e;
336
+ if (!(row === null || row === void 0 ? void 0 : row.id))
337
+ return;
338
+ // Seed the form from the row so Save is never sent against a stale id,
339
+ // then refresh the full record (relations) from the API.
340
+ dispatch({
341
+ type: WORKSPACE_USER_ACTION_TYPES.SET_FORM_DATA,
342
+ payload: {
343
+ form: {
344
+ enabled: (_a = row.enabled) !== null && _a !== void 0 ? _a : true,
345
+ id: row.id,
346
+ roleId: (_b = row.roleId) !== null && _b !== void 0 ? _b : null,
347
+ userId: (_c = row.userId) !== null && _c !== void 0 ? _c : "",
348
+ workspaceId: (_e = (_d = row.workspaceId) !== null && _d !== void 0 ? _d : workspace === null || workspace === void 0 ? void 0 : workspace.id) !== null && _e !== void 0 ? _e : "",
349
+ },
350
+ },
351
+ });
352
+ dispatch({
353
+ type: WORKSPACE_USER_ACTION_TYPES.SET_ERRORS,
354
+ payload: { errors: {} },
355
+ });
356
+ byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row.id } });
357
+ dispatch({
358
+ type: WORKSPACE_USER_ACTION_TYPES.SET_DRAWER,
359
+ payload: { drawer: WORKSPACE_USER_DRAWER.FORM_DRAWER },
360
+ });
361
+ }, [byIdFetchNow, dispatch, workspace === null || workspace === void 0 ? void 0 : workspace.id]);
310
362
  const handlePageChange = useCallback((page) => {
311
363
  const nextPage = typeof page === "number" ? page : state.currentPage + 1;
312
364
  dispatch({
@@ -358,20 +410,20 @@ export const useWorkspaceUserModule = () => {
358
410
  // ============================================================================
359
411
  const headerActions = useMemo(() => [
360
412
  {
361
- enabled: false,
413
+ enabled: true,
362
414
  handleOnClick: handleFilters,
363
415
  label: t("actionsButtonFilters"),
364
416
  order: 1,
365
417
  icon: Filter,
366
418
  },
367
419
  {
368
- enabled: false,
369
- handleOnClick: handleMoreActions,
370
- label: t("actionsButtonMoreActions"),
420
+ enabled: true,
421
+ handleOnClick: handleCreate,
422
+ label: t("actionsButtonAdd"),
371
423
  order: 2,
372
424
  icon: Plus,
373
425
  },
374
- ], [handleFilters, handleMoreActions, t]);
426
+ ], [handleCreate, handleFilters, t]);
375
427
  const rowActions = useMemo(() => [
376
428
  {
377
429
  enabled: true,
@@ -380,7 +432,22 @@ export const useWorkspaceUserModule = () => {
380
432
  order: 1,
381
433
  icon: Eye,
382
434
  },
383
- ], [handleView, t]);
435
+ {
436
+ enabled: true,
437
+ handleOnClick: handleEdit,
438
+ label: t("actionsButtonEdit"),
439
+ order: 2,
440
+ icon: Pencil,
441
+ },
442
+ {
443
+ enabled: true,
444
+ handleOnClick: handleDelete,
445
+ label: t("actionsButtonDelete"),
446
+ order: 3,
447
+ icon: Trash2,
448
+ variant: "destructive",
449
+ },
450
+ ], [handleDelete, handleEdit, handleView, t]);
384
451
  // ============================================================================
385
452
  // 1.4.9 EFFECTS
386
453
  // ============================================================================
@@ -2,12 +2,70 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
4
4
  import { EnhancedCheckbox } from "@appcorp/shadcn/components/enhanced-checkbox";
5
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
5
6
  import { useWorkspaceUserModule } from "./context";
6
7
  import { useTranslations } from "next-intl";
7
8
  import { getTranslatedError } from "../../utils/get-translated-error";
9
+ import { getCachedWorkspaceSync } from "../workspace/cache";
10
+ import { RBAC_API_ROUTES, USER_API_ROUTES } from "../../constants";
8
11
  export const WorkspaceUserForm = () => {
12
+ var _a;
9
13
  const { state, handleChange } = useWorkspaceUserModule();
10
14
  const { enabled, errors, roleId, userId, workspaceId } = state;
11
15
  const t = useTranslations("workspaceUser");
12
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold", children: t("formSectionWorkspaceUserInformation") }), _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedInput, { error: getTranslatedError({ errors, key: "workspaceId", t }), id: "workspaceId", info: t("formWorkspaceIdInfo"), label: t("formWorkspaceIdLabel"), onChange: (e) => handleChange("workspaceId", e.target.value), placeholder: t("formWorkspaceIdPlaceholder"), required: true, value: workspaceId }), _jsx(EnhancedInput, { error: getTranslatedError({ errors, key: "userId", t }), id: "userId", info: t("formUserIdInfo"), label: t("formUserIdLabel"), onChange: (e) => handleChange("userId", e.target.value), placeholder: t("formUserIdPlaceholder"), required: true, value: userId }), _jsx(EnhancedInput, { error: getTranslatedError({ errors, key: "roleId", t }), id: "roleId", info: t("formRoleIdInfo"), label: t("formRoleIdLabel"), onChange: (e) => handleChange("roleId", e.target.value), placeholder: t("formRoleIdPlaceholder"), value: roleId || "" })] }), _jsx(EnhancedCheckbox, { defaultChecked: enabled, label: t("badgeActive"), onCheckedChange: (checked) => handleChange("enabled", checked) })] }) }));
16
+ const workspace = getCachedWorkspaceSync();
17
+ const resolvedWorkspaceId = workspaceId || (workspace === null || workspace === void 0 ? void 0 : workspace.id) || "";
18
+ const workspaceLabel = ((_a = state.workspace) === null || _a === void 0 ? void 0 : _a.name) || (workspace === null || workspace === void 0 ? void 0 : workspace.name) || "";
19
+ const { enhancedComboboxElement: userIdCombo } = useEnhancedCombobox({
20
+ id: "userId",
21
+ error: getTranslatedError({ errors, key: "userId", t }),
22
+ info: t("formUserIdInfo"),
23
+ label: t("formUserIdLabel"),
24
+ onValueChange: (value) => handleChange("userId", value),
25
+ options: state.user
26
+ ? [
27
+ {
28
+ label: state.user.name || state.user.email || state.user.id,
29
+ value: state.user.id,
30
+ },
31
+ ]
32
+ : [],
33
+ placeholder: t("formUserIdPlaceholder"),
34
+ required: true,
35
+ searchEndpoint: USER_API_ROUTES.UNIT,
36
+ searchPlaceholder: t("formUserIdPlaceholder"),
37
+ emptyText: t("formNoUserFound"),
38
+ formatSearchResult: (item) => {
39
+ var _a;
40
+ return ({
41
+ label: String(item.name || item.email || item.id || ""),
42
+ value: String((_a = item.id) !== null && _a !== void 0 ? _a : ""),
43
+ });
44
+ },
45
+ value: userId,
46
+ });
47
+ const { enhancedComboboxElement: roleIdCombo } = useEnhancedCombobox({
48
+ id: "roleId",
49
+ error: getTranslatedError({ errors, key: "roleId", t }),
50
+ info: t("formRoleIdInfo"),
51
+ label: t("formRoleIdLabel"),
52
+ onValueChange: (value) => handleChange("roleId", value || ""),
53
+ options: state.role
54
+ ? [{ label: state.role.name || state.role.id, value: state.role.id }]
55
+ : [],
56
+ placeholder: t("formRoleIdPlaceholder"),
57
+ searchEndpoint: RBAC_API_ROUTES.UNIT,
58
+ searchParams: Object.assign(Object.assign({}, (resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {})), { includeSystem: "true" }),
59
+ searchPlaceholder: t("formRoleIdPlaceholder"),
60
+ emptyText: t("formNoRoleFound"),
61
+ formatSearchResult: (item) => {
62
+ var _a;
63
+ return ({
64
+ label: String(item.name || item.userRole || item.id || ""),
65
+ value: String((_a = item.id) !== null && _a !== void 0 ? _a : ""),
66
+ });
67
+ },
68
+ value: roleId || "",
69
+ });
70
+ return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold", children: t("formSectionWorkspaceUserInformation") }), _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedInput, { id: "workspaceId", error: getTranslatedError({ errors, key: "workspaceId", t }), info: t("formWorkspaceIdInfo"), label: t("formWorkspaceIdLabel"), onChange: (e) => handleChange("workspaceId", e.target.value), placeholder: t("formWorkspaceIdPlaceholder"), required: true, readOnly: true, value: workspaceLabel || resolvedWorkspaceId }), userIdCombo, roleIdCombo] }), _jsx(EnhancedCheckbox, { checked: enabled, label: t("badgeActive"), onCheckedChange: (checked) => handleChange("enabled", checked) })] }) }));
13
71
  };
@@ -9,8 +9,8 @@ import { useTranslations } from "next-intl";
9
9
  export const WorkspaceUserMoreActions = () => {
10
10
  const t = useTranslations("workspaceUser");
11
11
  const workspace = getCachedWorkspaceSync();
12
- const handleGetAllRecords = async (schoolId) => {
13
- const response = await fetch(`/api/workspace-user?pageLimit=1000&currentPage=1&schoolId=${schoolId}`, { method: API_METHODS.GET });
12
+ const handleGetAllRecords = async (workspaceId) => {
13
+ const response = await fetch(`/api/v1/workspace-user?pageLimit=1000&currentPage=1&workspaceId=${workspaceId}`, { method: API_METHODS.GET });
14
14
  const result = await response.json();
15
15
  const csv = await converter.json2csv(result.items || [], {});
16
16
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
@@ -35,8 +35,7 @@ export const WorkspaceUserMoreActions = () => {
35
35
  id: "1",
36
36
  title: t("downloadPopulatedCsvTemplate"),
37
37
  handleOnClick: async () => {
38
- var _a;
39
- await handleGetAllRecords(((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "");
38
+ await handleGetAllRecords((workspace === null || workspace === void 0 ? void 0 : workspace.id) || "");
40
39
  },
41
40
  },
42
41
  { id: "2", title: t("updateYourDataInTheCsv") },
@@ -127,10 +127,10 @@ const WorkspaceUserPageInner = (props) => {
127
127
  ]);
128
128
  const hasPermission = resolveRbacPermissions({
129
129
  userRole: props.userRole,
130
- moduleName: "People Management",
130
+ moduleName: "Workspace User",
131
131
  });
132
132
  if (!hasPermission) {
133
- return _jsx(RbacNoAccess, { moduleName: "People Management" });
133
+ return _jsx(RbacNoAccess, { moduleName: "Workspace User" });
134
134
  }
135
135
  return (_jsx("div", { className: "p-4", children: _jsx(GenericWorkspaceUserPage, { overrideConfig: workspaceUserConfig, context: context, tableBodyCols: tableBodyCols }) }));
136
136
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appcorp/fusion-storybook",
3
- "version": "0.4.69",
3
+ "version": "0.5.3",
4
4
  "scripts": {
5
5
  "build-storybook": "storybook build",
6
6
  "build:next": "next build",