@appcorp/fusion-storybook 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +147 -0
  2. package/base-modules/admission/cache.d.ts +14 -0
  3. package/base-modules/admission/cache.js +31 -0
  4. package/base-modules/admission/constants.d.ts +32 -0
  5. package/base-modules/admission/constants.js +37 -0
  6. package/base-modules/admission/context.d.ts +369 -0
  7. package/base-modules/admission/context.js +755 -0
  8. package/base-modules/admission/filter.d.ts +1 -0
  9. package/base-modules/admission/filter.js +31 -0
  10. package/base-modules/admission/form.d.ts +1 -0
  11. package/base-modules/admission/form.js +86 -0
  12. package/base-modules/admission/more-actions.d.ts +1 -0
  13. package/base-modules/admission/more-actions.js +5 -0
  14. package/base-modules/admission/page.d.ts +16 -0
  15. package/base-modules/admission/page.js +128 -0
  16. package/base-modules/admission/validate.d.ts +38 -0
  17. package/base-modules/admission/validate.js +70 -0
  18. package/base-modules/admission/view.d.ts +1 -0
  19. package/base-modules/admission/view.js +40 -0
  20. package/base-modules/discount-code/cache.d.ts +27 -0
  21. package/base-modules/discount-code/cache.js +46 -0
  22. package/base-modules/discount-code/constants.d.ts +19 -0
  23. package/base-modules/discount-code/constants.js +26 -0
  24. package/base-modules/discount-code/context.d.ts +154 -0
  25. package/base-modules/discount-code/context.js +416 -0
  26. package/base-modules/discount-code/filter.d.ts +9 -0
  27. package/base-modules/discount-code/filter.js +14 -0
  28. package/base-modules/discount-code/form.d.ts +1 -0
  29. package/base-modules/discount-code/form.js +44 -0
  30. package/base-modules/discount-code/more-actions.d.ts +9 -0
  31. package/base-modules/discount-code/more-actions.js +13 -0
  32. package/base-modules/discount-code/page.d.ts +16 -0
  33. package/base-modules/discount-code/page.js +120 -0
  34. package/base-modules/discount-code/validate.d.ts +19 -0
  35. package/base-modules/discount-code/validate.js +38 -0
  36. package/base-modules/discount-code/view.d.ts +1 -0
  37. package/base-modules/discount-code/view.js +26 -0
  38. package/base-modules/family/cache.d.ts +14 -0
  39. package/base-modules/family/cache.js +31 -0
  40. package/base-modules/family/constants.d.ts +22 -0
  41. package/base-modules/family/constants.js +26 -0
  42. package/base-modules/family/context.d.ts +173 -0
  43. package/base-modules/family/context.js +441 -0
  44. package/base-modules/family/filter.d.ts +1 -0
  45. package/base-modules/family/filter.js +28 -0
  46. package/base-modules/family/form.d.ts +1 -0
  47. package/base-modules/family/form.js +12 -0
  48. package/base-modules/family/more-actions.d.ts +1 -0
  49. package/base-modules/family/more-actions.js +5 -0
  50. package/base-modules/family/page.d.ts +16 -0
  51. package/base-modules/family/page.js +120 -0
  52. package/base-modules/family/validate.d.ts +15 -0
  53. package/base-modules/family/validate.js +18 -0
  54. package/base-modules/family/view.d.ts +1 -0
  55. package/base-modules/family/view.js +18 -0
  56. package/base-modules/family-member/cache.d.ts +14 -0
  57. package/base-modules/family-member/cache.js +31 -0
  58. package/base-modules/family-member/constants.d.ts +22 -0
  59. package/base-modules/family-member/constants.js +26 -0
  60. package/base-modules/family-member/context.d.ts +215 -0
  61. package/base-modules/family-member/context.js +486 -0
  62. package/base-modules/family-member/filter.d.ts +1 -0
  63. package/base-modules/family-member/filter.js +28 -0
  64. package/base-modules/family-member/form.d.ts +1 -0
  65. package/base-modules/family-member/form.js +17 -0
  66. package/base-modules/family-member/more-actions.d.ts +1 -0
  67. package/base-modules/family-member/more-actions.js +5 -0
  68. package/base-modules/family-member/page.d.ts +16 -0
  69. package/base-modules/family-member/page.js +122 -0
  70. package/base-modules/family-member/validate.d.ts +24 -0
  71. package/base-modules/family-member/validate.js +27 -0
  72. package/base-modules/family-member/view.d.ts +1 -0
  73. package/base-modules/family-member/view.js +40 -0
  74. package/base-modules/student-profile/cache.d.ts +14 -0
  75. package/base-modules/student-profile/cache.js +31 -0
  76. package/base-modules/student-profile/constants.d.ts +105 -0
  77. package/base-modules/student-profile/constants.js +132 -0
  78. package/base-modules/student-profile/context.d.ts +290 -0
  79. package/base-modules/student-profile/context.js +583 -0
  80. package/base-modules/student-profile/filter.d.ts +1 -0
  81. package/base-modules/student-profile/filter.js +26 -0
  82. package/base-modules/student-profile/form.d.ts +1 -0
  83. package/base-modules/student-profile/form.js +19 -0
  84. package/base-modules/student-profile/more-actions.d.ts +1 -0
  85. package/base-modules/student-profile/more-actions.js +5 -0
  86. package/base-modules/student-profile/page.d.ts +9 -0
  87. package/base-modules/student-profile/page.js +86 -0
  88. package/base-modules/student-profile/validate.d.ts +23 -0
  89. package/base-modules/student-profile/validate.js +34 -0
  90. package/base-modules/student-profile/view.d.ts +1 -0
  91. package/base-modules/student-profile/view.js +29 -0
  92. package/base-modules/workspace/cache.d.ts +9 -0
  93. package/base-modules/workspace/cache.js +28 -0
  94. package/base-modules/workspace/constants.d.ts +10 -0
  95. package/base-modules/workspace/constants.js +18 -0
  96. package/base-modules/workspace/context.d.ts +187 -0
  97. package/base-modules/workspace/context.js +293 -0
  98. package/base-modules/workspace/drawer.d.ts +1 -0
  99. package/base-modules/workspace/drawer.js +24 -0
  100. package/base-modules/workspace/filter.d.ts +1 -0
  101. package/base-modules/workspace/filter.js +13 -0
  102. package/base-modules/workspace/form.d.ts +1 -0
  103. package/base-modules/workspace/form.js +40 -0
  104. package/base-modules/workspace/more-actions.d.ts +1 -0
  105. package/base-modules/workspace/more-actions.js +13 -0
  106. package/base-modules/workspace/page.d.ts +1 -0
  107. package/base-modules/workspace/page.js +31 -0
  108. package/base-modules/workspace/validate.d.ts +9 -0
  109. package/base-modules/workspace/validate.js +8 -0
  110. package/base-modules/workspace/view.d.ts +1 -0
  111. package/base-modules/workspace/view.js +52 -0
  112. package/components/rbac-no-access.d.ts +6 -0
  113. package/components/rbac-no-access.js +4 -0
  114. package/constants.d.ts +81 -0
  115. package/constants.js +81 -0
  116. package/lib/utils.d.ts +2 -0
  117. package/lib/utils.js +5 -0
  118. package/package.json +104 -0
  119. package/tsconfig.build.tsbuildinfo +1 -0
  120. package/type.d.ts +1141 -0
  121. package/type.js +427 -0
  122. package/utils/admission-pdf.d.ts +78 -0
  123. package/utils/admission-pdf.js +73 -0
  124. package/utils/clear-cache.d.ts +1 -0
  125. package/utils/clear-cache.js +8 -0
  126. package/utils/format-value.d.ts +1 -0
  127. package/utils/format-value.js +1 -0
  128. package/utils/pdf-generator.d.ts +41 -0
  129. package/utils/pdf-generator.js +107 -0
  130. package/utils/resolve-rbac-permissions.d.ts +11 -0
  131. package/utils/resolve-rbac-permissions.js +251 -0
@@ -0,0 +1,441 @@
1
+ "use client";
2
+ /**
3
+ * Family Module — business logic + API integration
4
+ *
5
+ * Wires the generic module state (created by `createGenericModule`) to network
6
+ * actions using `useModuleEntityV2`. Contains domain-specific handlers for
7
+ * create/view/edit/delete operations with proper cache management and validation.
8
+ *
9
+ * Key responsibilities:
10
+ * - expose `useFamilyModule()` which UI components call for actions
11
+ * - keep module-specific `apiParams` and callbacks in one place
12
+ * - ensure cache invalidation and toast notifications on mutation
13
+ *
14
+ * Exported utilities:
15
+ * - `FamilyProvider` — provider component used by the page
16
+ * - `FamilyStateContextProvider` — backward-compat alias for dashboard-providers.tsx
17
+ * - `FAMILY_ACTION_TYPES` — action type constants (from factory)
18
+ * - `FAMILY_DRAWER` — drawer type constants
19
+ * - `useFamilyModule()` — hook that returns state, dispatch, and handlers
20
+ */
21
+ import { useCallback, useEffect, useMemo } from "react";
22
+ import { isCreatedOrUpdated, validateForm, } from "@react-pakistan/util-functions";
23
+ import { useModuleEntityV2, } from "@react-pakistan/util-functions/hooks/use-module-entity-v2";
24
+ import { useDebounce } from "@react-pakistan/util-functions/hooks/use-debounce";
25
+ import { createGenericModule } from "@react-pakistan/util-functions/factory/generic-module-factory";
26
+ import { DRAWER_TYPES } from "@react-pakistan/util-functions/factory/generic-component-factory";
27
+ import { FAMILY_API_ROUTES, pageLimit } from "./constants";
28
+ import { getCachedFamilies, invalidateFamiliesCache } from "./cache";
29
+ import { familyFormValidation } from "./validate";
30
+ import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
31
+ import { useTranslations } from "next-intl";
32
+ import { getCachedWorkspaceSync } from "../workspace/cache";
33
+ import { Eye, Filter, MoreHorizontal, Pencil, Plus } from "lucide-react";
34
+ // ============================================================================
35
+ // DRAWER TYPES
36
+ // ============================================================================
37
+ export const FAMILY_DRAWER = {
38
+ FILTER_DRAWER: DRAWER_TYPES.FILTER_DRAWER,
39
+ FORM_DRAWER: DRAWER_TYPES.FORM_DRAWER,
40
+ MORE_ACTIONS_DRAWER: "MORE_ACTIONS_DRAWER",
41
+ VIEW_DRAWER: DRAWER_TYPES.VIEW_DRAWER,
42
+ };
43
+ // ============================================================================
44
+ // CONFIGURATION
45
+ // ============================================================================
46
+ const familyConfig = {
47
+ name: "Family",
48
+ displayName: "Family",
49
+ initialState: {
50
+ // List Data
51
+ items: [],
52
+ count: 0,
53
+ // Search & Pagination
54
+ currentPage: 1,
55
+ pageLimit,
56
+ searchQuery: "",
57
+ // UI State
58
+ disableSaveButton: false,
59
+ drawer: null,
60
+ // Form State
61
+ address: "",
62
+ city: "",
63
+ country: "",
64
+ enabled: true,
65
+ errors: {},
66
+ familyCode: "",
67
+ filterEnabled: undefined,
68
+ id: "",
69
+ postalCode: "",
70
+ stateProvince: "", // renamed from 'state' to avoid conflict with factory state object
71
+ userId: "",
72
+ },
73
+ drawerTypes: DRAWER_TYPES,
74
+ };
75
+ // ============================================================================
76
+ // CREATE FAMILY MODULE
77
+ // ============================================================================
78
+ export const { actionTypes: FAMILY_ACTION_TYPES, config: familyModuleConfig, initialState: initialFamilyState, Provider: FamilyProvider, reducer: familyReducer, useContext: useFamilyContext, } = createGenericModule(familyConfig);
79
+ /**
80
+ * Backward-compatible alias used by dashboard-providers.tsx
81
+ */
82
+ export const FamilyStateContextProvider = FamilyProvider;
83
+ // ============================================================================
84
+ // ENHANCED FAMILY HOOK WITH API INTEGRATION
85
+ // ============================================================================
86
+ export const useFamilyModule = () => {
87
+ var _a, _b;
88
+ const context = useFamilyContext();
89
+ const t = useTranslations("family");
90
+ const workspace = getCachedWorkspaceSync();
91
+ const debouncedQuery = useDebounce(context.state.searchQuery, 800);
92
+ // ============================================================================
93
+ // API PARAMETERS
94
+ // ============================================================================
95
+ const listParams = useMemo(() => {
96
+ var _a;
97
+ return (Object.assign(Object.assign({ currentPage: context.state.currentPage, pageLimit: context.state.pageLimit, schoolId: ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "" }, (debouncedQuery ? { searchQuery: debouncedQuery } : {})), (context.state.filterEnabled !== undefined
98
+ ? { filterEnabled: String(context.state.filterEnabled) }
99
+ : {})));
100
+ }, [
101
+ context.state.currentPage,
102
+ context.state.filterEnabled,
103
+ context.state.pageLimit,
104
+ debouncedQuery,
105
+ (_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id,
106
+ ]);
107
+ const updateParams = useMemo(() => ({
108
+ address: context.state.address,
109
+ city: context.state.city,
110
+ country: context.state.country,
111
+ enabled: context.state.enabled,
112
+ familyCode: context.state.familyCode,
113
+ id: context.state.id,
114
+ postalCode: context.state.postalCode,
115
+ stateProvince: context.state.stateProvince,
116
+ }), [
117
+ context.state.address,
118
+ context.state.city,
119
+ context.state.country,
120
+ context.state.enabled,
121
+ context.state.familyCode,
122
+ context.state.id,
123
+ context.state.postalCode,
124
+ context.state.stateProvince,
125
+ ]);
126
+ const byIdParams = useMemo(() => ({ id: context.state.id }), [context.state.id]);
127
+ const deleteParams = useMemo(() => ({ id: context.state.id }), [context.state.id]);
128
+ // ============================================================================
129
+ // API CALLBACKS
130
+ // ============================================================================
131
+ const listCallback = ({ data, error }) => {
132
+ if (error) {
133
+ showToast(t("messagesFetchFailed"), TOAST_VARIANT.ERROR);
134
+ return;
135
+ }
136
+ if (data) {
137
+ context.dispatch({
138
+ type: FAMILY_ACTION_TYPES.SET_ITEMS,
139
+ payload: { items: data.items || [], count: data.count || 0 },
140
+ });
141
+ }
142
+ };
143
+ const updateCallback = ({ data, error }) => {
144
+ const isCreated = isCreatedOrUpdated(data);
145
+ if (error) {
146
+ showToast(isCreated ? t("messagesCreateFailed") : t("messagesUpdateFailed"), TOAST_VARIANT.ERROR);
147
+ return;
148
+ }
149
+ if (data) {
150
+ showToast(t("messagesSaveSuccess"), TOAST_VARIANT.SUCCESS);
151
+ invalidateFamiliesCache();
152
+ listFetchNow();
153
+ resetFormAndCloseDrawer();
154
+ }
155
+ };
156
+ const byIdCallback = ({ data, error }) => {
157
+ if (error) {
158
+ showToast(t("messagesDetailsFetchFailed"), TOAST_VARIANT.ERROR);
159
+ return;
160
+ }
161
+ if (data) {
162
+ const family = data;
163
+ setField("id", family.id);
164
+ setField("address", family.address);
165
+ setField("city", family.city);
166
+ setField("country", family.country);
167
+ setField("enabled", family.enabled);
168
+ setField("familyCode", family.familyCode);
169
+ setField("postalCode", family.postalCode);
170
+ setField("stateProvince", family.state);
171
+ context.dispatch({
172
+ type: FAMILY_ACTION_TYPES.SET_FORM_DATA,
173
+ payload: {
174
+ form: {
175
+ filterEnabled: undefined,
176
+ },
177
+ },
178
+ });
179
+ }
180
+ };
181
+ const deleteCallback = ({ data, error }) => {
182
+ if (error) {
183
+ showToast(t("messagesDeleteFailed"), TOAST_VARIANT.ERROR);
184
+ return;
185
+ }
186
+ if (data) {
187
+ showToast(t("messagesDeleteSuccess"), TOAST_VARIANT.SUCCESS);
188
+ invalidateFamiliesCache();
189
+ listFetchNow();
190
+ }
191
+ };
192
+ // ============================================================================
193
+ // API HOOKS
194
+ // ============================================================================
195
+ const { listFetchNow, listLoading, updateFetchNow, updateLoading, byIdFetchNow, deleteFetchNow, deleteLoading, byIdLoading, } = useModuleEntityV2({
196
+ byIdCallback,
197
+ byIdParams,
198
+ deleteCallback,
199
+ deleteParams,
200
+ listCallback,
201
+ listParams,
202
+ listUrl: FAMILY_API_ROUTES.UNIT,
203
+ searchQuery: debouncedQuery,
204
+ unitByIdUrl: FAMILY_API_ROUTES.UNIT,
205
+ unitUrl: FAMILY_API_ROUTES.UNIT,
206
+ updateCallback,
207
+ updateParams,
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ "x-api-token": process.env.NEXT_PUBLIC_API_KEY,
211
+ },
212
+ });
213
+ // ============================================================================
214
+ // UTILITIES
215
+ // ============================================================================
216
+ const showToast = useCallback((message, variant) => {
217
+ generateThemeToast({ description: message, variant });
218
+ }, []);
219
+ const resetFormAndCloseDrawer = useCallback(() => {
220
+ context.dispatch({
221
+ type: FAMILY_ACTION_TYPES.SET_ERRORS,
222
+ payload: { errors: {} },
223
+ });
224
+ context.dispatch({
225
+ type: FAMILY_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
226
+ payload: { disabled: false },
227
+ });
228
+ context.dispatch({
229
+ type: FAMILY_ACTION_TYPES.SET_DRAWER,
230
+ payload: { drawer: null },
231
+ });
232
+ }, [context]);
233
+ const setField = useCallback((key, value) => {
234
+ context.dispatch({
235
+ type: FAMILY_ACTION_TYPES.SET_INPUT_FIELD,
236
+ payload: { key, value: value === null ? undefined : value },
237
+ });
238
+ }, [context]);
239
+ // ============================================================================
240
+ // HANDLERS
241
+ // ============================================================================
242
+ const handleChange = useCallback((field, value) => {
243
+ context.dispatch({
244
+ type: FAMILY_ACTION_TYPES.SET_ERRORS,
245
+ payload: { errors: {} },
246
+ });
247
+ context.dispatch({
248
+ type: FAMILY_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
249
+ payload: { disabled: false },
250
+ });
251
+ setField(field, value);
252
+ }, [setField, context]);
253
+ const handlePageChange = useCallback((page) => {
254
+ context.dispatch({
255
+ type: FAMILY_ACTION_TYPES.SET_CURRENT_PAGE,
256
+ payload: { currentPage: page },
257
+ });
258
+ }, [context]);
259
+ const handlePageLimitChange = useCallback((limit) => {
260
+ context.dispatch({
261
+ type: FAMILY_ACTION_TYPES.SET_PAGE_LIMIT,
262
+ payload: { pageLimit: limit },
263
+ });
264
+ }, [context]);
265
+ const handleCloseDrawer = useCallback(() => {
266
+ resetFormAndCloseDrawer();
267
+ }, [resetFormAndCloseDrawer]);
268
+ const handleCreate = useCallback(() => {
269
+ context.dispatch({
270
+ type: FAMILY_ACTION_TYPES.SET_DRAWER,
271
+ payload: { drawer: FAMILY_DRAWER.FORM_DRAWER },
272
+ });
273
+ }, [context]);
274
+ const handleView = useCallback((row) => {
275
+ byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
276
+ context.dispatch({
277
+ type: FAMILY_ACTION_TYPES.SET_DRAWER,
278
+ payload: { drawer: FAMILY_DRAWER.VIEW_DRAWER },
279
+ });
280
+ }, [context, byIdFetchNow]);
281
+ const handleEdit = useCallback((row) => {
282
+ byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
283
+ context.dispatch({
284
+ type: FAMILY_ACTION_TYPES.SET_DRAWER,
285
+ payload: { drawer: FAMILY_DRAWER.FORM_DRAWER },
286
+ });
287
+ }, [context, byIdFetchNow]);
288
+ const handleDelete = useCallback((row) => {
289
+ if (!confirm(t("areYouSureYouWantToDeleteThisFamily")))
290
+ return;
291
+ deleteFetchNow === null || deleteFetchNow === void 0 ? void 0 : deleteFetchNow(undefined, {
292
+ body: JSON.stringify({ id: row === null || row === void 0 ? void 0 : row.id }),
293
+ });
294
+ }, [t, deleteFetchNow]);
295
+ const handleFilters = useCallback(() => {
296
+ context.dispatch({
297
+ type: FAMILY_ACTION_TYPES.SET_DRAWER,
298
+ payload: { drawer: FAMILY_DRAWER.FILTER_DRAWER },
299
+ });
300
+ }, [context]);
301
+ const handleMoreActions = useCallback(() => {
302
+ context.dispatch({
303
+ type: FAMILY_ACTION_TYPES.SET_DRAWER,
304
+ payload: { drawer: FAMILY_DRAWER.MORE_ACTIONS_DRAWER },
305
+ });
306
+ }, [context]);
307
+ const clearFilters = useCallback(() => {
308
+ context.dispatch({
309
+ type: FAMILY_ACTION_TYPES.SET_FILTERS,
310
+ payload: { filters: { filterEnabled: undefined } },
311
+ });
312
+ context.dispatch({
313
+ type: FAMILY_ACTION_TYPES.SET_CURRENT_PAGE,
314
+ payload: { currentPage: 1 },
315
+ });
316
+ }, [context]);
317
+ const handleSearch = useCallback((query) => {
318
+ context.dispatch({
319
+ type: FAMILY_ACTION_TYPES.SET_SEARCH_QUERY,
320
+ payload: { searchQuery: query },
321
+ });
322
+ }, [context]);
323
+ const handleSubmit = useCallback(() => {
324
+ context.dispatch({
325
+ type: FAMILY_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
326
+ payload: { disabled: true },
327
+ });
328
+ validateForm({
329
+ params: updateParams,
330
+ schema: familyFormValidation,
331
+ successCallback: () => {
332
+ updateFetchNow(undefined, {
333
+ body: JSON.stringify(updateParams),
334
+ });
335
+ },
336
+ errorCallback: (errors) => {
337
+ context.dispatch({
338
+ type: FAMILY_ACTION_TYPES.SET_ERRORS,
339
+ payload: { errors },
340
+ });
341
+ context.dispatch({
342
+ type: FAMILY_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
343
+ payload: { disabled: false },
344
+ });
345
+ showToast(t("messagesFormErrors"), TOAST_VARIANT.ERROR);
346
+ },
347
+ });
348
+ }, [context, updateParams, t, showToast, updateFetchNow]);
349
+ const applyFilters = useCallback(() => {
350
+ context.dispatch({
351
+ type: FAMILY_ACTION_TYPES.SET_CURRENT_PAGE,
352
+ payload: { currentPage: 1 },
353
+ });
354
+ listFetchNow();
355
+ handleCloseDrawer();
356
+ }, [context, listFetchNow, handleCloseDrawer]);
357
+ const headerActions = useMemo(() => [
358
+ {
359
+ enabled: false,
360
+ handleOnClick: handleMoreActions,
361
+ label: t("headerActionsMoreActions"),
362
+ order: 1,
363
+ icon: MoreHorizontal,
364
+ },
365
+ {
366
+ enabled: true,
367
+ handleOnClick: handleFilters,
368
+ label: t("headerActionsFilters"),
369
+ order: 2,
370
+ icon: Filter,
371
+ },
372
+ {
373
+ enabled: false,
374
+ handleOnClick: handleCreate,
375
+ label: t("headerActionsAddFamily"),
376
+ order: 3,
377
+ icon: Plus,
378
+ },
379
+ ], [handleFilters, handleMoreActions, handleCreate, t]);
380
+ const rowActions = useMemo(() => [
381
+ {
382
+ enabled: true,
383
+ handleOnClick: handleView,
384
+ label: t("rowActionsView"),
385
+ order: 1,
386
+ icon: Eye,
387
+ },
388
+ {
389
+ enabled: true,
390
+ handleOnClick: handleEdit,
391
+ label: t("rowActionsEdit"),
392
+ order: 2,
393
+ icon: Pencil,
394
+ },
395
+ ], [handleView, handleEdit, t]);
396
+ // ============================================================================
397
+ // EFFECTS
398
+ // ============================================================================
399
+ useEffect(() => {
400
+ var _a;
401
+ if (!((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id))
402
+ return;
403
+ (async () => {
404
+ try {
405
+ const { count, items } = await getCachedFamilies({
406
+ params: listParams,
407
+ });
408
+ context.dispatch({
409
+ type: FAMILY_ACTION_TYPES.SET_ITEMS,
410
+ payload: { items: items || [], count: count || 0 },
411
+ });
412
+ }
413
+ catch (_a) {
414
+ showToast(t("messagesFetchFailed"), TOAST_VARIANT.ERROR);
415
+ }
416
+ })();
417
+ }, [listParams, (_b = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _b === void 0 ? void 0 : _b.id, context, showToast, t]);
418
+ // ============================================================================
419
+ // RETURN
420
+ // ============================================================================
421
+ return Object.assign(Object.assign({}, context), { applyFilters,
422
+ byIdLoading,
423
+ clearFilters,
424
+ deleteLoading,
425
+ handleChange,
426
+ handleCloseDrawer,
427
+ handleCreate,
428
+ handleDelete,
429
+ handleEdit,
430
+ handleFilters,
431
+ handleMoreActions,
432
+ handlePageChange,
433
+ handlePageLimitChange,
434
+ handleSearch,
435
+ handleSubmit,
436
+ handleView,
437
+ headerActions,
438
+ listLoading,
439
+ rowActions,
440
+ updateLoading });
441
+ };
@@ -0,0 +1 @@
1
+ export declare const FamilyFilter: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Family Filter Component
5
+ *
6
+ * Filter component for family records.
7
+ */
8
+ import { EnhancedRadio } from "@appcorp/shadcn/components/enhanced-radio";
9
+ import { useFamilyModule } from "./context";
10
+ import { useTranslations } from "next-intl";
11
+ export const FamilyFilter = () => {
12
+ const { state, handleChange } = useFamilyModule();
13
+ const { filterEnabled } = state;
14
+ const t = useTranslations("family");
15
+ const filterEnabledValue = filterEnabled === undefined
16
+ ? "undefined"
17
+ : filterEnabled
18
+ ? "true"
19
+ : "false";
20
+ return (_jsx("div", { className: "space-y-4", children: _jsx("div", { className: "grid grid-cols-1 gap-4", children: _jsx(EnhancedRadio, { label: t("enabled"), name: "filterEnabled", value: filterEnabledValue, options: [
21
+ { label: t("all"), value: "undefined" },
22
+ { label: t("enabled"), value: "true" },
23
+ { label: t("disabled"), value: "false" },
24
+ ], onValueChange: (next) => {
25
+ const parsed = next === "true" ? true : next === "false" ? false : undefined;
26
+ handleChange("filterEnabled", parsed);
27
+ } }) }) }));
28
+ };
@@ -0,0 +1 @@
1
+ export declare const FamilyForm: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EnhancedCheckbox } from "@appcorp/shadcn/components/enhanced-checkbox";
4
+ import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
5
+ import { useFamilyModule } from "./context";
6
+ import { useTranslations } from "next-intl";
7
+ export const FamilyForm = () => {
8
+ const { state, handleChange } = useFamilyModule();
9
+ const { address, city, country, enabled, errors, familyCode, postalCode, stateProvince, } = state;
10
+ const t = useTranslations("family");
11
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedInput, { disabled: true, error: errors.familyCode, id: "familyCode", info: t("familyCodeAutoGenerated"), label: t("familyCode"), onChange: (e) => handleChange("familyCode", e.target.value), placeholder: "FAM2026001", required: true, value: familyCode }), _jsx(EnhancedInput, { error: errors.address, id: "address", info: t("streetAddressPlaceholder"), label: t("streetAddress"), onChange: (e) => handleChange("address", e.target.value), placeholder: "123 Main Street", value: address || "" }), _jsx(EnhancedInput, { error: errors.city, id: "city", info: t("cityName"), label: t("city"), onChange: (e) => handleChange("city", e.target.value), placeholder: "New York", value: city || "" }), _jsx(EnhancedInput, { error: errors.stateProvince, id: "stateProvince", info: t("stateOrProvince"), label: t("stateProvince"), onChange: (e) => handleChange("stateProvince", e.target.value), placeholder: "NY", value: stateProvince || "" }), _jsx(EnhancedInput, { error: errors.country, id: "country", info: t("countryName"), label: t("country"), onChange: (e) => handleChange("country", e.target.value), placeholder: "United States", value: country || "" }), _jsx(EnhancedInput, { error: errors.postalCode, id: "postalCode", info: t("postalOrZipCode"), label: t("postalCode"), onChange: (e) => handleChange("postalCode", e.target.value), placeholder: "10001", value: postalCode || "" }), _jsx(EnhancedCheckbox, { checked: enabled, id: "enabled", label: t("active"), onCheckedChange: (checked) => handleChange("enabled", checked) })] }));
12
+ };
@@ -0,0 +1 @@
1
+ export declare const FamilyMoreActions: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ export const FamilyMoreActions = () => {
4
+ return _jsx("div", { className: "space-y-4", children: "More actions for families" });
5
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Family Page Component
3
+ *
4
+ * Thin wrapper around GenericModulePage. All handlers, header actions, and row
5
+ * actions live in context.tsx — this file only owns:
6
+ * - tableBodyCols (static, module-level constant)
7
+ * - familyConfig (memoised on locale change only; size fixed so the
8
+ * component type produced by createGenericModulePage never changes at
9
+ * runtime, preventing the infinite-remount re-render loop)
10
+ * - permission guard
11
+ */
12
+ import { FC } from "react";
13
+ import { USER_ROLE } from "@/type";
14
+ export declare const FamilyPage: FC<{
15
+ userRole: USER_ROLE;
16
+ }>;
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Family Page Component
5
+ *
6
+ * Thin wrapper around GenericModulePage. All handlers, header actions, and row
7
+ * actions live in context.tsx — this file only owns:
8
+ * - tableBodyCols (static, module-level constant)
9
+ * - familyConfig (memoised on locale change only; size fixed so the
10
+ * component type produced by createGenericModulePage never changes at
11
+ * runtime, preventing the infinite-remount re-render loop)
12
+ * - permission guard
13
+ */
14
+ import { useMemo } from "react";
15
+ import { COMPONENT_TYPE } from "@appcorp/shadcn/components/enhanced-table";
16
+ import { createGenericModulePage, } from "@react-pakistan/util-functions/factory/generic-component-factory";
17
+ import { useFamilyModule, FamilyProvider, FAMILY_ACTION_TYPES, } from "./context";
18
+ import { FamilyFilter } from "./filter";
19
+ import { FamilyForm } from "./form";
20
+ import { FamilyMoreActions } from "./more-actions";
21
+ import { FamilyView } from "./view";
22
+ // import { useAuthStateContext } from "@/contexts/auth-context";
23
+ import { resolveRbacPermissions } from "@/utils/resolve-rbac-permissions";
24
+ import { RbacNoAccess } from "@/components/rbac-no-access";
25
+ import { useTranslations } from "next-intl";
26
+ // ============================================================================
27
+ // TABLE COLUMN CONFIGURATION (static — no runtime deps)
28
+ // ============================================================================
29
+ const tableBodyCols = [
30
+ { componentType: COMPONENT_TYPE.ID, key: "id" },
31
+ { componentType: COMPONENT_TYPE.BOLD_TEXT, key: "familyCode" },
32
+ { componentType: COMPONENT_TYPE.OBJECT, key: ["user:email"] },
33
+ { componentType: COMPONENT_TYPE.TEXT, key: "address" },
34
+ { componentType: COMPONENT_TYPE.TEXT, key: "city" },
35
+ { componentType: COMPONENT_TYPE.BOOLEAN, key: "enabled" },
36
+ { componentType: COMPONENT_TYPE.ACTIONS },
37
+ ];
38
+ const tableColumns = [
39
+ { label: "id", width: "5%" },
40
+ { label: "familyCode", width: "20%" },
41
+ { label: "primaryEmail", width: "20%" },
42
+ { label: "address", width: "20%" },
43
+ { label: "city", width: "15%" },
44
+ { label: "status", width: "15%" },
45
+ { label: "actions", width: "5%" },
46
+ ];
47
+ // ============================================================================
48
+ // TRANSLATED COLUMNS HELPER
49
+ // ============================================================================
50
+ const getTranslatedColumns = (t) => tableColumns.map((col) => (Object.assign(Object.assign({}, col), { label: t(col.label) })));
51
+ // ============================================================================
52
+ // COMPONENT FACTORY (creates JSX elements when config is created, not during render)
53
+ // ============================================================================
54
+ const createComponentInstances = () => ({
55
+ filter: _jsx(FamilyFilter, {}),
56
+ form: _jsx(FamilyForm, {}),
57
+ moreActions: _jsx(FamilyMoreActions, {}),
58
+ view: _jsx(FamilyView, {}),
59
+ });
60
+ // ============================================================================
61
+ // CONFIG CREATION HELPER
62
+ // ============================================================================
63
+ const createFamilyConfig = (t, dispatch) => {
64
+ const components = createComponentInstances();
65
+ return {
66
+ moduleName: "family",
67
+ tableColumns: getTranslatedColumns(t),
68
+ cancelLabel: t("cancel"),
69
+ drawerTitle: t("family"),
70
+ filterContent: components.filter,
71
+ formContent: components.form,
72
+ moreActionsContent: components.moreActions,
73
+ saveLabel: t("save"),
74
+ searchPlaceholder: t("searchFamilies"),
75
+ tableDescription: t("manageFamilyAccountsAndTheirInformation"),
76
+ tableTitle: t("family"),
77
+ viewContent: components.view,
78
+ onClearFilters: () => {
79
+ dispatch({ type: FAMILY_ACTION_TYPES.RESET_FORM });
80
+ },
81
+ };
82
+ };
83
+ // ============================================================================
84
+ // STABLE PAGE COMPONENT (created once, outside render)
85
+ // ============================================================================
86
+ const GenericFamilyPage = createGenericModulePage({
87
+ moduleName: "family",
88
+ tableColumns: [],
89
+ cancelLabel: "",
90
+ drawerTitle: "",
91
+ filterContent: _jsx(FamilyFilter, {}),
92
+ formContent: _jsx(FamilyForm, {}),
93
+ moreActionsContent: _jsx(FamilyMoreActions, {}),
94
+ saveLabel: "",
95
+ searchPlaceholder: "",
96
+ tableDescription: "",
97
+ tableTitle: "",
98
+ viewContent: _jsx(FamilyView, {}),
99
+ size: "small",
100
+ onClearFilters: () => { },
101
+ });
102
+ // ============================================================================
103
+ // INNER PAGE (requires FamilyProvider context)
104
+ // ============================================================================
105
+ const FamilyPageInner = ({ userRole }) => {
106
+ const t = useTranslations("family");
107
+ const context = useFamilyModule();
108
+ // Memoize permission check to avoid recalculation on every render
109
+ const hasPermission = useMemo(() => resolveRbacPermissions({
110
+ userRole,
111
+ moduleName: "People Management",
112
+ }), [userRole]);
113
+ // Memoize config creation
114
+ const familyConfig = useMemo(() => createFamilyConfig(t, context.dispatch), [t, context.dispatch]);
115
+ if (!hasPermission) {
116
+ return _jsx(RbacNoAccess, { moduleName: "People Management" });
117
+ }
118
+ return (_jsx("div", { className: "p-4", children: _jsx(GenericFamilyPage, { config: familyConfig, context: context, tableBodyCols: tableBodyCols }) }));
119
+ };
120
+ export const FamilyPage = ({ userRole }) => (_jsx(FamilyProvider, { children: _jsx(FamilyPageInner, { userRole: userRole }) }));
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Family Validation Schema
3
+ *
4
+ * Zod validation schemas for family form data.
5
+ */
6
+ import { z } from "zod";
7
+ export declare const familyFormValidation: z.ZodObject<{
8
+ familyCode: z.ZodString;
9
+ address: z.ZodOptional<z.ZodString>;
10
+ city: z.ZodOptional<z.ZodString>;
11
+ stateProvince: z.ZodOptional<z.ZodString>;
12
+ country: z.ZodOptional<z.ZodString>;
13
+ postalCode: z.ZodOptional<z.ZodString>;
14
+ enabled: z.ZodOptional<z.ZodBoolean>;
15
+ }, z.core.$strip>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Family Validation Schema
3
+ *
4
+ * Zod validation schemas for family form data.
5
+ */
6
+ import { z } from "zod";
7
+ // ============================================================================
8
+ // VALIDATION SCHEMA
9
+ // ============================================================================
10
+ export const familyFormValidation = z.object({
11
+ familyCode: z.string().min(1, "Family code is required"),
12
+ address: z.string().optional(),
13
+ city: z.string().optional(),
14
+ stateProvince: z.string().optional(),
15
+ country: z.string().optional(),
16
+ postalCode: z.string().optional(),
17
+ enabled: z.boolean().optional(),
18
+ });
@@ -0,0 +1 @@
1
+ export declare const FamilyView: () => import("react/jsx-runtime").JSX.Element;