@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,755 @@
1
+ "use client";
2
+ /**
3
+ * Admission 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, AI analysis, PDF printing, and admit-student flow.
8
+ *
9
+ * Key responsibilities:
10
+ * - expose `useAdmissionModule()` 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
+ * - `AdmissionProvider` — provider component used by the page
16
+ * - `AdmissionStateContextProvider` — backward-compat alias for dashboard-providers.tsx
17
+ * - `ADMISSION_ACTION_TYPES` — action type constants (from factory)
18
+ * - `ADMISSION_DRAWER` — drawer type constants
19
+ * - `useAdmissionModule()` — hook that returns state, dispatch, and handlers
20
+ */
21
+ import { useCallback, useEffect, useMemo } from "react";
22
+ import { validateForm, isCreatedOrUpdated, API_METHODS, fetchData, } from "@react-pakistan/util-functions";
23
+ import { formatCnic } from "@react-pakistan/util-functions/general/cnic-formatter";
24
+ import { formatPhone } from "@react-pakistan/util-functions/general/format-phone";
25
+ import { useModuleEntityV2, } from "@react-pakistan/util-functions/hooks/use-module-entity-v2";
26
+ import { useDebounce } from "@react-pakistan/util-functions/hooks/use-debounce";
27
+ import { createGenericModule } from "@react-pakistan/util-functions/factory/generic-module-factory";
28
+ import { DRAWER_TYPES } from "@react-pakistan/util-functions/factory/generic-component-factory";
29
+ import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
30
+ import { ADMISSION_STATUS, GENDER, } from "../../type";
31
+ import { useTranslations } from "next-intl";
32
+ import { ADMISSION_API_ROUTES, pageLimit } from "./constants";
33
+ import { admissionFormValidation } from "./validate";
34
+ import { getCachedAdmissions, invalidateAdmissionsCache } from "./cache";
35
+ import { getCachedWorkspaceSync } from "../workspace/cache";
36
+ import { generateAdmissionReceiptPDF, } from "../../utils/admission-pdf";
37
+ import { invalidateFamiliesCache } from "../family/cache";
38
+ import { invalidateFamilyMembersCache } from "../family-member/cache";
39
+ import { invalidateStudentProfilesCache } from "../student-profile/cache";
40
+ import { Filter, Plus } from "lucide-react";
41
+ // ============================================================================
42
+ // DRAWER TYPES
43
+ // ============================================================================
44
+ export const ADMISSION_DRAWER = {
45
+ FILTER_DRAWER: DRAWER_TYPES.FILTER_DRAWER,
46
+ FORM_DRAWER: DRAWER_TYPES.FORM_DRAWER,
47
+ MORE_ACTIONS_DRAWER: "MORE_ACTIONS_DRAWER",
48
+ VIEW_DRAWER: DRAWER_TYPES.VIEW_DRAWER,
49
+ };
50
+ // ============================================================================
51
+ // CONFIGURATION
52
+ // ============================================================================
53
+ const admissionConfig = {
54
+ name: "Admission",
55
+ displayName: "Admission",
56
+ initialState: {
57
+ // List data
58
+ items: [],
59
+ count: 0,
60
+ // Search & Pagination
61
+ currentPage: 1,
62
+ pageLimit,
63
+ searchQuery: "",
64
+ // UI State
65
+ disableSaveButton: false,
66
+ drawer: null,
67
+ errors: {},
68
+ // Form — student detail
69
+ id: "",
70
+ bForm: "",
71
+ discountCode: "",
72
+ dob: new Date().toISOString().slice(0, 10),
73
+ emergencyContact: "Mother",
74
+ firstName: "",
75
+ gender: GENDER.MALE,
76
+ hafiz: false,
77
+ lastName: "",
78
+ orphan: false,
79
+ registrationCode: "",
80
+ // Form — father detail
81
+ fatherCnic: "",
82
+ fatherFirstName: "",
83
+ fatherLastName: "",
84
+ fatherMobile: "",
85
+ fatherOccupation: "",
86
+ fatherOrganization: "",
87
+ // Form — mother detail
88
+ motherCnic: "",
89
+ motherFirstName: "",
90
+ motherLastName: "",
91
+ motherMobile: "",
92
+ // Form — home detail
93
+ address: "",
94
+ city: "",
95
+ country: "",
96
+ postalCode: "",
97
+ state: "",
98
+ // Form — admission detail
99
+ classForAdmission: "",
100
+ previousSchool: "",
101
+ siblings: "",
102
+ // Form — office use
103
+ admissionNotes: "",
104
+ notes: "",
105
+ // Status / meta
106
+ enabled: true,
107
+ status: ADMISSION_STATUS.PENDING,
108
+ admissionStatus: ADMISSION_STATUS.PENDING,
109
+ schoolId: "",
110
+ familyId: null,
111
+ // AI analysis state
112
+ aiAnalysis: null,
113
+ analyzeLoading: false,
114
+ analyzeError: null,
115
+ // Filter State
116
+ filterEnabled: undefined,
117
+ filterAdmissionStatus: undefined,
118
+ filterStartDate: undefined,
119
+ filterEndDate: undefined,
120
+ },
121
+ drawerTypes: DRAWER_TYPES,
122
+ };
123
+ // ============================================================================
124
+ // CREATE ADMISSION MODULE
125
+ // ============================================================================
126
+ export const { actionTypes: ADMISSION_ACTION_TYPES, config: admissionModuleConfig, initialState: initialAdmissionState, Provider: AdmissionProvider, reducer: admissionReducer, useContext: useAdmissionContext, } = createGenericModule(admissionConfig);
127
+ // ============================================================================
128
+ // ENHANCED ADMISSION HOOK WITH API INTEGRATION
129
+ // ============================================================================
130
+ export const useAdmissionModule = () => {
131
+ var _a, _b, _c, _d, _e;
132
+ const context = useAdmissionContext();
133
+ const t = useTranslations("admission");
134
+ const workspace = getCachedWorkspaceSync();
135
+ const debouncedQuery = useDebounce(context.state.searchQuery, 800);
136
+ // ============================================================================
137
+ // API PARAMETERS
138
+ // ============================================================================
139
+ const listParams = useMemo(() => {
140
+ var _a;
141
+ return (Object.assign(Object.assign(Object.assign(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
142
+ ? { filterEnabled: String(context.state.filterEnabled) }
143
+ : {})), (context.state.filterAdmissionStatus
144
+ ? { filterAdmissionStatus: context.state.filterAdmissionStatus }
145
+ : {})), (context.state.filterStartDate
146
+ ? { filterStartDate: context.state.filterStartDate }
147
+ : {})), (context.state.filterEndDate
148
+ ? { filterEndDate: context.state.filterEndDate }
149
+ : {})));
150
+ }, [
151
+ context.state.currentPage,
152
+ context.state.filterAdmissionStatus,
153
+ context.state.filterEnabled,
154
+ context.state.filterEndDate,
155
+ context.state.filterStartDate,
156
+ context.state.pageLimit,
157
+ debouncedQuery,
158
+ (_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id,
159
+ ]);
160
+ const updateParams = useMemo(() => {
161
+ var _a, _b, _c, _d;
162
+ return ({
163
+ address: context.state.address || "",
164
+ admissionNotes: context.state.admissionNotes || "",
165
+ admissionStatus: context.state.status || ADMISSION_STATUS.PENDING,
166
+ bForm: context.state.bForm || "",
167
+ city: context.state.city || "",
168
+ classForAdmission: context.state.classForAdmission || "",
169
+ country: context.state.country || "",
170
+ discountCode: context.state.discountCode || "",
171
+ dob: context.state.dob || null,
172
+ emergencyContact: context.state.emergencyContact || "",
173
+ enabled: (_a = context.state.enabled) !== null && _a !== void 0 ? _a : true,
174
+ fatherCnic: context.state.fatherCnic || "",
175
+ fatherFirstName: context.state.fatherFirstName || "",
176
+ fatherLastName: context.state.fatherLastName || "",
177
+ fatherMobile: context.state.fatherMobile || "",
178
+ fatherOccupation: context.state.fatherOccupation || "",
179
+ fatherOrganization: context.state.fatherOrganization || "",
180
+ firstName: context.state.firstName || "",
181
+ gender: context.state.gender || null,
182
+ hafiz: (_b = context.state.hafiz) !== null && _b !== void 0 ? _b : false,
183
+ id: context.state.id || "",
184
+ lastName: context.state.lastName || "",
185
+ motherCnic: context.state.motherCnic || "",
186
+ motherFirstName: context.state.motherFirstName || "",
187
+ motherLastName: context.state.motherLastName || "",
188
+ motherMobile: context.state.motherMobile || "",
189
+ notes: context.state.notes || "",
190
+ orphan: (_c = context.state.orphan) !== null && _c !== void 0 ? _c : false,
191
+ postalCode: context.state.postalCode || "",
192
+ previousSchool: context.state.previousSchool || "",
193
+ registrationCode: context.state.registrationCode || "",
194
+ schoolId: ((_d = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _d === void 0 ? void 0 : _d.id) || "",
195
+ siblings: context.state.siblings || "",
196
+ state: context.state.state || "",
197
+ });
198
+ }, [context.state, (_b = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _b === void 0 ? void 0 : _b.id]);
199
+ const byIdParams = useMemo(() => ({ id: context.state.id }), [context.state.id]);
200
+ const deleteParams = useMemo(() => ({ id: context.state.id }), [context.state.id]);
201
+ // ============================================================================
202
+ // API CALLBACKS
203
+ // ============================================================================
204
+ const listCallback = ({ data, error }) => {
205
+ if (error) {
206
+ showToast(t("messagesFetchFailed"), TOAST_VARIANT.ERROR);
207
+ return;
208
+ }
209
+ if (data) {
210
+ context.dispatch({
211
+ type: ADMISSION_ACTION_TYPES.SET_ITEMS,
212
+ payload: { items: data.items || [], count: data.count || 0 },
213
+ });
214
+ }
215
+ };
216
+ const updateCallback = ({ data, error }) => {
217
+ const isCreated = isCreatedOrUpdated(data);
218
+ if (error) {
219
+ showToast(isCreated ? t("messagesCreateFailed") : t("messagesUpdateFailed"), TOAST_VARIANT.ERROR);
220
+ return;
221
+ }
222
+ if (data) {
223
+ showToast(t("messagesSaveSuccess"), TOAST_VARIANT.SUCCESS);
224
+ invalidateAdmissionsCache();
225
+ listFetchNow();
226
+ resetFormAndCloseDrawer();
227
+ }
228
+ };
229
+ const byIdCallback = ({ data, error }) => {
230
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
231
+ if (error) {
232
+ showToast(t("messagesDetailsFetchFailed"), TOAST_VARIANT.ERROR);
233
+ return;
234
+ }
235
+ if (data) {
236
+ const admission = data;
237
+ setField("id", admission.id);
238
+ setField("firstName", (_a = admission.studentDetails) === null || _a === void 0 ? void 0 : _a.firstName);
239
+ setField("lastName", (_b = admission.studentDetails) === null || _b === void 0 ? void 0 : _b.lastName);
240
+ setField("bForm", (_c = admission.studentDetails) === null || _c === void 0 ? void 0 : _c.bForm);
241
+ // Normalise gender to match GENDER enum values (e.g. 'Male' → 'MALE')
242
+ const rawGender = (_d = admission.studentDetails) === null || _d === void 0 ? void 0 : _d.gender;
243
+ let normalizedGender = null;
244
+ if (typeof rawGender === "string" && rawGender.length > 0) {
245
+ const upper = rawGender.toUpperCase();
246
+ normalizedGender = Object.values(GENDER).includes(upper)
247
+ ? upper
248
+ : rawGender;
249
+ }
250
+ setField("gender", normalizedGender);
251
+ setField("dob", (_e = admission.studentDetails) === null || _e === void 0 ? void 0 : _e.dob);
252
+ setField("registrationCode", (_f = admission.studentDetails) === null || _f === void 0 ? void 0 : _f.registrationCode);
253
+ setField("discountCode", (_g = admission.studentDetails) === null || _g === void 0 ? void 0 : _g.discountCode);
254
+ setField("hafiz", (_h = admission.studentDetails) === null || _h === void 0 ? void 0 : _h.hafiz);
255
+ setField("orphan", (_j = admission.studentDetails) === null || _j === void 0 ? void 0 : _j.orphan);
256
+ setField("fatherFirstName", (_k = admission.fatherDetails) === null || _k === void 0 ? void 0 : _k.fatherFirstName);
257
+ setField("fatherLastName", (_l = admission.fatherDetails) === null || _l === void 0 ? void 0 : _l.fatherLastName);
258
+ setField("fatherCnic", (_m = admission.fatherDetails) === null || _m === void 0 ? void 0 : _m.fatherCnic);
259
+ setField("fatherMobile", (_o = admission.fatherDetails) === null || _o === void 0 ? void 0 : _o.fatherMobile);
260
+ setField("fatherOccupation", (_p = admission.fatherDetails) === null || _p === void 0 ? void 0 : _p.fatherOccupation);
261
+ setField("fatherOrganization", (_q = admission.fatherDetails) === null || _q === void 0 ? void 0 : _q.fatherOrganization);
262
+ setField("motherFirstName", (_r = admission.motherDetails) === null || _r === void 0 ? void 0 : _r.motherFirstName);
263
+ setField("motherLastName", (_s = admission.motherDetails) === null || _s === void 0 ? void 0 : _s.motherLastName);
264
+ setField("motherCnic", (_t = admission.motherDetails) === null || _t === void 0 ? void 0 : _t.motherCnic);
265
+ setField("motherMobile", (_u = admission.motherDetails) === null || _u === void 0 ? void 0 : _u.motherMobile);
266
+ setField("address", (_v = admission.homeDetails) === null || _v === void 0 ? void 0 : _v.address);
267
+ setField("city", (_w = admission.homeDetails) === null || _w === void 0 ? void 0 : _w.city);
268
+ setField("country", (_x = admission.homeDetails) === null || _x === void 0 ? void 0 : _x.country);
269
+ setField("postalCode", (_y = admission.homeDetails) === null || _y === void 0 ? void 0 : _y.postalCode);
270
+ setField("state", (_z = admission.homeDetails) === null || _z === void 0 ? void 0 : _z.state);
271
+ setField("notes", (_0 = admission.officeUse) === null || _0 === void 0 ? void 0 : _0.notes);
272
+ setField("admissionNotes", (_1 = admission.officeUse) === null || _1 === void 0 ? void 0 : _1.admissionNotes);
273
+ setField("previousSchool", (_2 = admission.admissionDetails) === null || _2 === void 0 ? void 0 : _2.previousSchool);
274
+ setField("siblings", (_3 = admission.admissionDetails) === null || _3 === void 0 ? void 0 : _3.siblings);
275
+ setField("classForAdmission", (_4 = admission.admissionDetails) === null || _4 === void 0 ? void 0 : _4.classForAdmission);
276
+ setField("status", admission.status || ADMISSION_STATUS.PENDING);
277
+ setField("enabled", (_5 = admission.enabled) !== null && _5 !== void 0 ? _5 : true);
278
+ // Populate AI analysis if present
279
+ const aiAnalysis = (_6 = admission
280
+ .aiAnalysis) !== null && _6 !== void 0 ? _6 : null;
281
+ context.dispatch({
282
+ type: ADMISSION_ACTION_TYPES.SET_FORM_DATA,
283
+ payload: { form: { aiAnalysis } },
284
+ });
285
+ }
286
+ };
287
+ const deleteCallback = ({ data, error }) => {
288
+ if (error) {
289
+ showToast(t("messagesDeleteFailed"), TOAST_VARIANT.ERROR);
290
+ return;
291
+ }
292
+ if (data) {
293
+ showToast(t("messagesDeleteSuccess"), TOAST_VARIANT.SUCCESS);
294
+ invalidateAdmissionsCache();
295
+ listFetchNow();
296
+ }
297
+ };
298
+ // ============================================================================
299
+ // API HOOKS
300
+ // ============================================================================
301
+ const { listFetchNow, listLoading, updateFetchNow, updateLoading, byIdFetchNow, deleteFetchNow, deleteLoading, byIdLoading, } = useModuleEntityV2({
302
+ byIdCallback,
303
+ byIdParams,
304
+ deleteCallback,
305
+ deleteParams,
306
+ listCallback,
307
+ listParams,
308
+ listUrl: ADMISSION_API_ROUTES.UNIT,
309
+ searchQuery: debouncedQuery,
310
+ unitByIdUrl: ADMISSION_API_ROUTES.UNIT,
311
+ unitUrl: ADMISSION_API_ROUTES.UNIT,
312
+ updateCallback,
313
+ updateParams,
314
+ headers: {
315
+ "Content-Type": "application/json",
316
+ "x-api-token": process.env.NEXT_PUBLIC_API_KEY,
317
+ },
318
+ });
319
+ // ============================================================================
320
+ // UTILITIES
321
+ // ============================================================================
322
+ const showToast = useCallback((message, variant) => {
323
+ generateThemeToast({ description: message, variant });
324
+ }, []);
325
+ const resetFormAndCloseDrawer = useCallback(() => {
326
+ context.dispatch({
327
+ type: ADMISSION_ACTION_TYPES.SET_ERRORS,
328
+ payload: { errors: {} },
329
+ });
330
+ context.dispatch({
331
+ type: ADMISSION_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
332
+ payload: { disabled: false },
333
+ });
334
+ context.dispatch({
335
+ type: ADMISSION_ACTION_TYPES.SET_DRAWER,
336
+ payload: { drawer: null },
337
+ });
338
+ }, [context]);
339
+ const setField = useCallback((key, value) => {
340
+ var _a, _b, _c;
341
+ let formatted = value === null
342
+ ? undefined
343
+ : value;
344
+ if ((key === "bForm" || key === "fatherCnic" || key === "motherCnic") &&
345
+ typeof value === "string" &&
346
+ value.length === 13) {
347
+ formatted = (_a = formatCnic(value)) !== null && _a !== void 0 ? _a : value;
348
+ }
349
+ if ((key === "fatherMobile" || key === "motherMobile") &&
350
+ typeof value === "string" &&
351
+ value.length >= 13) {
352
+ formatted = (_c = (_b = formatPhone(value, "PK")) === null || _b === void 0 ? void 0 : _b.international) !== null && _c !== void 0 ? _c : value;
353
+ }
354
+ context.dispatch({
355
+ type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
356
+ payload: { key, value: formatted || value },
357
+ });
358
+ }, [context]);
359
+ // ============================================================================
360
+ // HANDLERS
361
+ // ============================================================================
362
+ const handleChange = useCallback((field, value) => {
363
+ context.dispatch({
364
+ type: ADMISSION_ACTION_TYPES.SET_ERRORS,
365
+ payload: { errors: {} },
366
+ });
367
+ context.dispatch({
368
+ type: ADMISSION_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
369
+ payload: { disabled: false },
370
+ });
371
+ setField(field, value);
372
+ }, [setField, context]);
373
+ const handleCloseDrawer = useCallback(() => {
374
+ resetFormAndCloseDrawer();
375
+ }, [resetFormAndCloseDrawer]);
376
+ const handleCreate = useCallback(() => {
377
+ context.dispatch({
378
+ type: ADMISSION_ACTION_TYPES.SET_DRAWER,
379
+ payload: { drawer: ADMISSION_DRAWER.FORM_DRAWER },
380
+ });
381
+ }, [context]);
382
+ const handleView = useCallback((row) => {
383
+ byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
384
+ context.dispatch({
385
+ type: ADMISSION_ACTION_TYPES.SET_DRAWER,
386
+ payload: { drawer: ADMISSION_DRAWER.VIEW_DRAWER },
387
+ });
388
+ }, [context, byIdFetchNow]);
389
+ const handleEdit = useCallback((row) => {
390
+ byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
391
+ context.dispatch({
392
+ type: ADMISSION_ACTION_TYPES.SET_DRAWER,
393
+ payload: { drawer: ADMISSION_DRAWER.FORM_DRAWER },
394
+ });
395
+ }, [context, byIdFetchNow]);
396
+ const handleDelete = useCallback((row) => {
397
+ if (!confirm(t("areYouSureYouWantToDeleteThisAdmission")))
398
+ return;
399
+ deleteFetchNow === null || deleteFetchNow === void 0 ? void 0 : deleteFetchNow(undefined, {
400
+ body: JSON.stringify({ id: row === null || row === void 0 ? void 0 : row.id }),
401
+ });
402
+ }, [t, deleteFetchNow]);
403
+ const handleFilters = useCallback(() => {
404
+ context.dispatch({
405
+ type: ADMISSION_ACTION_TYPES.SET_DRAWER,
406
+ payload: { drawer: ADMISSION_DRAWER.FILTER_DRAWER },
407
+ });
408
+ }, [context]);
409
+ const handlePageChange = useCallback((page) => {
410
+ context.dispatch({
411
+ type: ADMISSION_ACTION_TYPES.SET_CURRENT_PAGE,
412
+ payload: { currentPage: page },
413
+ });
414
+ }, [context]);
415
+ const handlePageLimitChange = useCallback((limit) => {
416
+ context.dispatch({
417
+ type: ADMISSION_ACTION_TYPES.SET_PAGE_LIMIT,
418
+ payload: { pageLimit: limit },
419
+ });
420
+ }, [context]);
421
+ const handleSearch = useCallback((query) => {
422
+ context.dispatch({
423
+ type: ADMISSION_ACTION_TYPES.SET_SEARCH_QUERY,
424
+ payload: { searchQuery: query },
425
+ });
426
+ }, [context]);
427
+ const handleSetStartDate = useCallback((startDate) => {
428
+ context.dispatch({
429
+ type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
430
+ payload: { key: "filterStartDate", value: startDate },
431
+ });
432
+ }, [context]);
433
+ const handleSetEndDate = useCallback((endDate) => {
434
+ context.dispatch({
435
+ type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
436
+ payload: { key: "filterEndDate", value: endDate },
437
+ });
438
+ }, [context]);
439
+ const clearFilters = useCallback(() => {
440
+ context.dispatch({
441
+ type: ADMISSION_ACTION_TYPES.SET_FILTERS,
442
+ payload: {
443
+ filters: {
444
+ filterEnabled: undefined,
445
+ filterAdmissionStatus: undefined,
446
+ filterStartDate: undefined,
447
+ filterEndDate: undefined,
448
+ appliedFilterEnabled: undefined,
449
+ appliedFilterAdmissionStatus: undefined,
450
+ appliedFilterStartDate: undefined,
451
+ appliedFilterEndDate: undefined,
452
+ },
453
+ },
454
+ });
455
+ context.dispatch({
456
+ type: ADMISSION_ACTION_TYPES.SET_CURRENT_PAGE,
457
+ payload: { currentPage: 1 },
458
+ });
459
+ }, [context]);
460
+ const handlePrint = useCallback(async (row) => {
461
+ var _a, _b, _c, _d, _e;
462
+ if (!row)
463
+ return;
464
+ const logoBase64Res = await fetch(`/api/v1/auth/image-url-to-base64`, {
465
+ method: API_METHODS.POST,
466
+ body: JSON.stringify({ url: (_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.logo }),
467
+ headers: {
468
+ "Content-Type": "application/json",
469
+ "x-api-token": process.env.NEXT_PUBLIC_API_KEY,
470
+ },
471
+ });
472
+ const logoBase64 = await logoBase64Res.json();
473
+ try {
474
+ const rowRecord = row;
475
+ const studentDetails = rowRecord.studentDetails || {};
476
+ const admissionDetails = rowRecord.admissionDetails || {};
477
+ const fatherDetails = rowRecord.fatherDetails || {};
478
+ const motherDetails = rowRecord.motherDetails || {};
479
+ const homeDetails = rowRecord.homeDetails || {};
480
+ const pdfData = {
481
+ firstName: studentDetails.firstName || "",
482
+ lastName: studentDetails.lastName || "",
483
+ bForm: studentDetails.bForm || "",
484
+ gender: studentDetails.gender || "",
485
+ dob: studentDetails.dob || "",
486
+ registrationCode: studentDetails.registrationCode || "",
487
+ discountCode: studentDetails.discountCode,
488
+ fatherFirstName: fatherDetails.fatherFirstName || "",
489
+ fatherLastName: fatherDetails.fatherLastName || "",
490
+ fatherCnic: fatherDetails.fatherCnic || "",
491
+ fatherMobile: fatherDetails.fatherMobile || "",
492
+ fatherOccupation: fatherDetails.fatherOccupation,
493
+ fatherOrganization: fatherDetails.fatherOrganization,
494
+ motherFirstName: motherDetails.motherFirstName || "",
495
+ motherLastName: motherDetails.motherLastName || "",
496
+ motherCnic: motherDetails.motherCnic || "",
497
+ motherMobile: motherDetails.motherMobile || "",
498
+ address: homeDetails.address || "",
499
+ city: homeDetails.city || "",
500
+ state: homeDetails.state || "",
501
+ country: homeDetails.country || "",
502
+ classForAdmission: admissionDetails.classForAdmission || "",
503
+ previousSchool: admissionDetails.previousSchool,
504
+ siblings: admissionDetails.siblings,
505
+ notes: admissionDetails.notes,
506
+ admissionNotes: admissionDetails.admissionNotes,
507
+ schoolName: ((_b = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _b === void 0 ? void 0 : _b.name) || undefined,
508
+ schoolAddress: ((_c = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _c === void 0 ? void 0 : _c.address) || undefined,
509
+ schoolPhone: ((_d = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _d === void 0 ? void 0 : _d.phone) || undefined,
510
+ schoolEmail: ((_e = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _e === void 0 ? void 0 : _e.email) || undefined,
511
+ submissionDate: (row === null || row === void 0 ? void 0 : row.createdAt)
512
+ ? new Date(row === null || row === void 0 ? void 0 : row.createdAt).toLocaleDateString()
513
+ : undefined,
514
+ admissionStatus: admissionDetails.admissionStatus,
515
+ logo: logoBase64.base64,
516
+ };
517
+ await generateAdmissionReceiptPDF(pdfData);
518
+ }
519
+ catch (_f) {
520
+ showToast(t("messagesPrintFailed"), TOAST_VARIANT.ERROR);
521
+ }
522
+ }, [workspace, t, showToast]);
523
+ const handleAddStudent = useCallback(async (row) => {
524
+ const tableRow = row;
525
+ const { data, error } = await fetchData({
526
+ url: "/api/v1/admit-student",
527
+ method: API_METHODS.POST,
528
+ headers: {
529
+ "Content-Type": "application/json",
530
+ "x-api-token": process.env.NEXT_PUBLIC_API_KEY,
531
+ },
532
+ body: JSON.stringify({ admissionId: tableRow === null || tableRow === void 0 ? void 0 : tableRow.id }),
533
+ });
534
+ listFetchNow();
535
+ invalidateAdmissionsCache();
536
+ invalidateFamiliesCache();
537
+ invalidateFamilyMembersCache();
538
+ invalidateStudentProfilesCache();
539
+ if (data) {
540
+ showToast(t("messagesAdmitSuccess"), TOAST_VARIANT.SUCCESS);
541
+ }
542
+ if (error) {
543
+ showToast(t("messagesAdmitFailed"), TOAST_VARIANT.ERROR);
544
+ }
545
+ }, [t, showToast, listFetchNow]);
546
+ // ============================================================================
547
+ // NETWORK ACTIONS
548
+ // ============================================================================
549
+ const handleAnalyze = useCallback(async (row) => {
550
+ var _a, _b, _c;
551
+ const tableRow = row;
552
+ if (!(tableRow === null || tableRow === void 0 ? void 0 : tableRow.id))
553
+ return;
554
+ const schoolId = (_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id;
555
+ if (!schoolId)
556
+ return;
557
+ context.dispatch({
558
+ type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
559
+ payload: { key: "analyzeLoading", value: true },
560
+ });
561
+ context.dispatch({
562
+ type: ADMISSION_ACTION_TYPES.SET_FORM_DATA,
563
+ payload: { form: { analyzeError: null } },
564
+ });
565
+ // Open view drawer so user can watch progress
566
+ await (byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: tableRow.id } }));
567
+ context.dispatch({
568
+ type: ADMISSION_ACTION_TYPES.SET_DRAWER,
569
+ payload: { drawer: ADMISSION_DRAWER.VIEW_DRAWER },
570
+ });
571
+ const { data, error } = await fetchData({
572
+ url: `/api/v1/admission/analyze`,
573
+ method: API_METHODS.POST,
574
+ headers: {
575
+ "Content-Type": "application/json",
576
+ "x-api-token": process.env.NEXT_PUBLIC_API_KEY,
577
+ },
578
+ body: JSON.stringify({ id: tableRow.id, schoolId }),
579
+ });
580
+ context.dispatch({
581
+ type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
582
+ payload: { key: "analyzeLoading", value: false },
583
+ });
584
+ if (error) {
585
+ context.dispatch({
586
+ type: ADMISSION_ACTION_TYPES.SET_FORM_DATA,
587
+ payload: { form: { analyzeError: t("messagesAnalyzeFailed") } },
588
+ });
589
+ showToast(t("messagesAnalyzeFailed"), TOAST_VARIANT.ERROR);
590
+ return;
591
+ }
592
+ const result = data;
593
+ if (result === null || result === void 0 ? void 0 : result.aiAnalysis) {
594
+ context.dispatch({
595
+ type: ADMISSION_ACTION_TYPES.SET_FORM_DATA,
596
+ payload: { form: { aiAnalysis: result.aiAnalysis } },
597
+ });
598
+ }
599
+ invalidateAdmissionsCache();
600
+ listFetchNow();
601
+ generateThemeToast({
602
+ description: `${t("messagesAnalyzeSuccess")} ${(_b = result === null || result === void 0 ? void 0 : result.score) !== null && _b !== void 0 ? _b : "N/A"} — ${(_c = result === null || result === void 0 ? void 0 : result.status) !== null && _c !== void 0 ? _c : ""}`,
603
+ variant: TOAST_VARIANT.SUCCESS,
604
+ });
605
+ }, [(_c = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _c === void 0 ? void 0 : _c.id, t, showToast, byIdFetchNow, listFetchNow, context]);
606
+ const handleSubmit = useCallback(() => {
607
+ context.dispatch({
608
+ type: ADMISSION_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
609
+ payload: { disabled: true },
610
+ });
611
+ validateForm({
612
+ params: updateParams,
613
+ schema: admissionFormValidation,
614
+ successCallback: () => {
615
+ updateFetchNow(undefined, {
616
+ body: JSON.stringify(updateParams),
617
+ });
618
+ },
619
+ errorCallback: (errors) => {
620
+ context.dispatch({
621
+ type: ADMISSION_ACTION_TYPES.SET_ERRORS,
622
+ payload: { errors },
623
+ });
624
+ context.dispatch({
625
+ type: ADMISSION_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
626
+ payload: { disabled: false },
627
+ });
628
+ showToast(t("messagesFormErrors"), TOAST_VARIANT.ERROR);
629
+ },
630
+ });
631
+ }, [context, updateParams, t, showToast, updateFetchNow]);
632
+ // ============================================================================
633
+ // HEADER & ROW ACTIONS
634
+ // ============================================================================
635
+ const hasGeminiSecrets = Boolean(((_d = workspace === null || workspace === void 0 ? void 0 : workspace.secrets) === null || _d === void 0 ? void 0 : _d.GEMINI_API_KEY) && ((_e = workspace === null || workspace === void 0 ? void 0 : workspace.secrets) === null || _e === void 0 ? void 0 : _e.GEMINI_MODEL));
636
+ const headerActions = useMemo(() => [
637
+ {
638
+ enabled: true,
639
+ handleOnClick: handleFilters,
640
+ label: t("headerActionsFilters"),
641
+ order: 1,
642
+ icon: Filter,
643
+ },
644
+ {
645
+ enabled: true,
646
+ handleOnClick: handleCreate,
647
+ label: t("headerActionsAddAdmission"),
648
+ order: 2,
649
+ icon: Plus,
650
+ },
651
+ ], [handleFilters, handleCreate, t]);
652
+ const rowActions = useMemo(() => [
653
+ {
654
+ enabled: true,
655
+ handleOnClick: handleView,
656
+ label: t("rowActionsView"),
657
+ order: 1,
658
+ },
659
+ {
660
+ enabled: true,
661
+ handleOnClick: handleEdit,
662
+ label: t("rowActionsEdit"),
663
+ order: 2,
664
+ },
665
+ {
666
+ enabled: false,
667
+ handleOnClick: handleDelete,
668
+ label: t("rowActionsDelete"),
669
+ order: 3,
670
+ },
671
+ {
672
+ enabled: true,
673
+ handleOnClick: handlePrint,
674
+ label: t("rowActionsPrintPdf"),
675
+ order: 4,
676
+ },
677
+ {
678
+ enabled: hasGeminiSecrets,
679
+ handleOnClick: handleAnalyze,
680
+ label: t("rowActionsAnalyzeWithAi"),
681
+ order: 5,
682
+ },
683
+ {
684
+ enabled: (row) => (row === null || row === void 0 ? void 0 : row.status) !== ADMISSION_STATUS.APPROVED,
685
+ handleOnClick: handleAddStudent,
686
+ label: t("rowActionsAddStudent"),
687
+ order: 6,
688
+ },
689
+ ], [
690
+ handleView,
691
+ handleEdit,
692
+ handleDelete,
693
+ handlePrint,
694
+ handleAnalyze,
695
+ handleAddStudent,
696
+ hasGeminiSecrets,
697
+ t,
698
+ ]);
699
+ const applyFilters = useCallback(() => {
700
+ context.dispatch({
701
+ type: ADMISSION_ACTION_TYPES.SET_CURRENT_PAGE,
702
+ payload: { currentPage: 1 },
703
+ });
704
+ listFetchNow();
705
+ handleCloseDrawer();
706
+ }, [context, listFetchNow, handleCloseDrawer]);
707
+ // ============================================================================
708
+ // EFFECTS
709
+ // ============================================================================
710
+ useEffect(() => {
711
+ if (!(workspace === null || workspace === void 0 ? void 0 : workspace.id))
712
+ return;
713
+ (async () => {
714
+ try {
715
+ const { count, items } = await getCachedAdmissions({
716
+ params: listParams,
717
+ });
718
+ context.dispatch({
719
+ type: ADMISSION_ACTION_TYPES.SET_ITEMS,
720
+ payload: { items: items || [], count: count || 0 },
721
+ });
722
+ }
723
+ catch (_a) {
724
+ showToast(t("messagesFetchFailed"), TOAST_VARIANT.ERROR);
725
+ }
726
+ })();
727
+ }, [listParams, workspace === null || workspace === void 0 ? void 0 : workspace.id, t, showToast, context]);
728
+ // ============================================================================
729
+ // RETURN
730
+ // ============================================================================
731
+ return Object.assign(Object.assign({}, context), { applyFilters,
732
+ byIdLoading,
733
+ clearFilters,
734
+ deleteLoading,
735
+ handleAddStudent,
736
+ handleAnalyze,
737
+ handleChange,
738
+ handleCloseDrawer,
739
+ handleCreate,
740
+ handleDelete,
741
+ handleEdit,
742
+ handleFilters,
743
+ handlePageChange,
744
+ handlePageLimitChange,
745
+ handlePrint,
746
+ handleSearch,
747
+ handleSetEndDate,
748
+ handleSetStartDate,
749
+ handleSubmit,
750
+ handleView,
751
+ headerActions,
752
+ listLoading,
753
+ rowActions,
754
+ updateLoading });
755
+ };