@appcorp/fusion-storybook 0.2.40 → 0.2.44

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 (100) hide show
  1. package/base-modules/admission/constants.d.ts +5 -17
  2. package/base-modules/admission/constants.js +12 -7
  3. package/base-modules/admission/context/use-admission-module.js +11 -48
  4. package/base-modules/admission/filter.js +23 -3
  5. package/base-modules/admission/form.js +49 -19
  6. package/base-modules/attendance/context.js +3 -37
  7. package/base-modules/attendance/filter.js +3 -1
  8. package/base-modules/attendance/form.js +26 -10
  9. package/base-modules/attendance/more-actions.js +34 -25
  10. package/base-modules/campus/context.js +7 -7
  11. package/base-modules/class/cache.js +0 -1
  12. package/base-modules/class/context.js +10 -48
  13. package/base-modules/class/more-actions.js +47 -24
  14. package/base-modules/course/context.js +3 -37
  15. package/base-modules/course/form.js +91 -292
  16. package/base-modules/discount-code/constants.d.ts +5 -0
  17. package/base-modules/discount-code/constants.js +5 -0
  18. package/base-modules/discount-code/context.d.ts +1 -0
  19. package/base-modules/discount-code/context.js +40 -39
  20. package/base-modules/discount-code/form.js +21 -15
  21. package/base-modules/discount-code/more-actions.js +1 -1
  22. package/base-modules/enrollment/context.js +3 -37
  23. package/base-modules/enrollment/form.js +38 -11
  24. package/base-modules/enrollment/more-actions.js +48 -25
  25. package/base-modules/expense/constants.js +1 -1
  26. package/base-modules/expense/context.js +5 -32
  27. package/base-modules/expense/filter.js +50 -3
  28. package/base-modules/expense/form.js +82 -6
  29. package/base-modules/family/context.js +7 -38
  30. package/base-modules/family-member/context.js +7 -39
  31. package/base-modules/fee-structure/context.js +1 -25
  32. package/base-modules/fee-structure/form.js +77 -89
  33. package/base-modules/fee-structure/more-actions.js +45 -22
  34. package/base-modules/rbac/context.d.ts +1 -0
  35. package/base-modules/rbac/context.js +24 -33
  36. package/base-modules/rbac/form.js +3 -1
  37. package/base-modules/school/context.js +1 -1
  38. package/base-modules/school/form.js +34 -14
  39. package/base-modules/section/context.d.ts +1 -0
  40. package/base-modules/section/context.js +40 -47
  41. package/base-modules/section/form.js +25 -80
  42. package/base-modules/section/more-actions.js +47 -24
  43. package/base-modules/section/view.js +9 -7
  44. package/base-modules/student-fee/context/use-student-fee-module.d.ts +1 -0
  45. package/base-modules/student-fee/context/use-student-fee-module.js +48 -32
  46. package/base-modules/student-fee/context.d.ts +1 -1
  47. package/base-modules/student-fee/context.js +1 -1
  48. package/base-modules/student-fee/filter.js +23 -3
  49. package/base-modules/student-fee/form.js +93 -174
  50. package/base-modules/student-fee/view.d.ts +7 -1
  51. package/base-modules/student-fee/view.js +17 -20
  52. package/base-modules/student-profile/constants.d.ts +0 -6
  53. package/base-modules/student-profile/constants.js +1 -3
  54. package/base-modules/student-profile/context/use-student-profile-module.d.ts +1 -0
  55. package/base-modules/student-profile/context/use-student-profile-module.js +62 -55
  56. package/base-modules/student-profile/context.d.ts +1 -1
  57. package/base-modules/student-profile/context.js +1 -1
  58. package/base-modules/student-profile/filter.js +24 -4
  59. package/base-modules/student-profile/form.js +35 -3
  60. package/base-modules/subject/context.d.ts +1 -0
  61. package/base-modules/subject/context.js +38 -47
  62. package/base-modules/subject/more-actions.js +47 -24
  63. package/base-modules/teacher/constants.d.ts +0 -6
  64. package/base-modules/teacher/constants.js +0 -2
  65. package/base-modules/teacher/context.d.ts +1 -0
  66. package/base-modules/teacher/context.js +58 -39
  67. package/base-modules/teacher/form.js +46 -11
  68. package/base-modules/teacher/more-actions.js +45 -22
  69. package/base-modules/user/context/use-user-module.d.ts +1 -0
  70. package/base-modules/user/context/use-user-module.js +36 -32
  71. package/base-modules/user/context.js +1 -1
  72. package/base-modules/user/filter.js +6 -4
  73. package/base-modules/user/form.js +29 -5
  74. package/base-modules/user/more-actions.js +9 -7
  75. package/base-modules/user/view.js +3 -1
  76. package/base-modules/workspace/form.js +18 -8
  77. package/base-modules/workspace-user/context.d.ts +2 -1
  78. package/base-modules/workspace-user/context.js +31 -29
  79. package/package.json +1 -1
  80. package/tsconfig.build.tsbuildinfo +1 -1
  81. package/base-modules/admission/cache.d.ts +0 -14
  82. package/base-modules/admission/cache.js +0 -31
  83. package/base-modules/attendance/cache.d.ts +0 -14
  84. package/base-modules/attendance/cache.js +0 -31
  85. package/base-modules/course/cache.d.ts +0 -14
  86. package/base-modules/course/cache.js +0 -31
  87. package/base-modules/enrollment/cache.d.ts +0 -14
  88. package/base-modules/enrollment/cache.js +0 -31
  89. package/base-modules/expense/cache.d.ts +0 -14
  90. package/base-modules/expense/cache.js +0 -31
  91. package/base-modules/family/cache.d.ts +0 -14
  92. package/base-modules/family/cache.js +0 -31
  93. package/base-modules/family-member/cache.d.ts +0 -14
  94. package/base-modules/family-member/cache.js +0 -31
  95. package/base-modules/rbac/cache.d.ts +0 -27
  96. package/base-modules/rbac/cache.js +0 -46
  97. package/base-modules/student-fee/cache.d.ts +0 -15
  98. package/base-modules/student-fee/cache.js +0 -21
  99. package/base-modules/workspace-user/cache.d.ts +0 -14
  100. package/base-modules/workspace-user/cache.js +0 -31
@@ -7,7 +7,6 @@ import converter from "json-2-csv";
7
7
  import { Timeline } from "../../components/timeline";
8
8
  import { useTranslations } from "next-intl";
9
9
  import { SUBJECT_API_ROUTES, pageLimit } from "./constants";
10
- import { invalidateSubjectsCache } from "./cache";
11
10
  import { SUBJECT_ACTION_TYPES, useSubjectContext } from "./context";
12
11
  import { useRef, useEffect, useCallback } from "react";
13
12
  const workspace = getCachedWorkspaceSync();
@@ -68,13 +67,17 @@ async function pollBulkJob(jobId, signal, onProgress) {
68
67
  }
69
68
  }
70
69
  }
71
- function formatErrorSummary(errors) {
70
+ function formatErrorSummary(
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ t, errors) {
72
73
  if (!(errors === null || errors === void 0 ? void 0 : errors.length))
73
74
  return "";
74
- const lines = errors.slice(0, 5).map((e) => `Row ${e.row}: ${e.error}`);
75
+ const lines = errors
76
+ .slice(0, 5)
77
+ .map((e) => t("messagesBulkRowError", { row: e.row, error: e.error }));
75
78
  const remaining = errors.length - 5;
76
79
  if (remaining > 0)
77
- lines.push(`...and ${remaining} more`);
80
+ lines.push(t("messagesBulkMoreRows", { count: remaining }));
78
81
  return lines.join("\n");
79
82
  }
80
83
  export const SubjectMoreActions = () => {
@@ -107,7 +110,7 @@ export const SubjectMoreActions = () => {
107
110
  const text = await file.text();
108
111
  const records = converter.csv2json(text);
109
112
  if (!Array.isArray(records) || records.length === 0) {
110
- showErrorToast("CSV file is empty or invalid");
113
+ showErrorToast(t("messagesBulkCsvEmpty"));
111
114
  return;
112
115
  }
113
116
  // Client-side validation — basic required field check
@@ -117,55 +120,72 @@ export const SubjectMoreActions = () => {
117
120
  const msgs = [];
118
121
  if (method === "POST") {
119
122
  if (!((_b = row.code) === null || _b === void 0 ? void 0 : _b.trim()))
120
- msgs.push("code is required");
123
+ msgs.push(t("validationRequiredCode"));
121
124
  if (!((_c = row.name) === null || _c === void 0 ? void 0 : _c.trim()))
122
- msgs.push("name is required");
125
+ msgs.push(t("validationRequiredName"));
123
126
  }
124
127
  else {
125
128
  if (!((_d = row.id) === null || _d === void 0 ? void 0 : _d.trim()))
126
- msgs.push("id is required for update");
129
+ msgs.push(t("validationRequiredIdForUpdate"));
127
130
  }
128
131
  if (msgs.length > 0) {
129
132
  validationErrors.push({ row: i + 1, messages: msgs });
130
133
  }
131
134
  }
132
135
  if (validationErrors.length > 0) {
133
- const summary = validationErrors
136
+ const errorsList = validationErrors
134
137
  .slice(0, 5)
135
- .map((e) => `Row ${e.row}: ${e.messages.join("; ")}`)
138
+ .map((e) => t("messagesBulkRowError", {
139
+ row: e.row,
140
+ error: e.messages.join("; "),
141
+ }))
136
142
  .join("\n");
137
143
  const remaining = validationErrors.length - 5;
138
- showErrorToast(`Validation failed for ${validationErrors.length} row(s).\n${summary}${remaining > 0 ? `\n...and ${remaining} more` : ""}`);
144
+ const errorsStr = remaining > 0
145
+ ? `${errorsList}\n${t("messagesBulkMoreRows", { count: remaining })}`
146
+ : errorsList;
147
+ showErrorToast(t("messagesBulkValidationFailed", {
148
+ count: validationErrors.length,
149
+ errors: errorsStr,
150
+ }));
139
151
  return;
140
152
  }
141
153
  try {
142
- showInfoToast(`Bulk ${label} job queued (${records.length} records). Processing...`);
154
+ showInfoToast(t("messagesBulkJobQueued", { action: label, count: records.length }));
143
155
  let jobId;
144
156
  try {
145
157
  jobId = await submitBulkJob(text, method, signal);
146
158
  }
147
159
  catch (submitError) {
148
- showErrorToast(`Failed to submit ${label} job: ${submitError instanceof Error ? submitError.message : "Unknown error"}`);
160
+ showErrorToast(t("messagesBulkJobSubmitFailed", {
161
+ action: label,
162
+ error: submitError instanceof Error
163
+ ? submitError.message
164
+ : t("unknownError"),
165
+ }));
149
166
  return;
150
167
  }
151
168
  const status = await pollBulkJob(jobId, signal, (processed, total) => {
152
- showInfoToast(`Processing ${processed}/${total} subjects...`);
169
+ showInfoToast(t("messagesBulkProgress", { processed, total }));
153
170
  });
154
171
  if (signal.aborted)
155
172
  return;
156
173
  if (status.status === "completed") {
157
174
  const r = status.results;
158
175
  if (r && ((_e = r.errors) === null || _e === void 0 ? void 0 : _e.length) > 0) {
159
- const summary = formatErrorSummary(r.errors);
160
- showSuccessToast(`Created ${r.created} | Updated ${r.updated} | Skipped ${r.skipped}\n${summary}`);
176
+ const summary = formatErrorSummary(t, r.errors);
177
+ showSuccessToast(`${t("messagesBulkResults", { created: r.created, updated: r.updated, skipped: r.skipped })}\n${summary}`);
161
178
  }
162
179
  else if (r) {
163
- showSuccessToast(`Created ${r.created} | Updated ${r.updated} | Skipped ${r.skipped}`);
180
+ showSuccessToast(t("messagesBulkResults", {
181
+ created: r.created,
182
+ updated: r.updated,
183
+ skipped: r.skipped,
184
+ }));
164
185
  }
165
186
  else {
166
- showSuccessToast("Bulk operation completed successfully");
187
+ showSuccessToast(t("messagesBulkSuccess"));
167
188
  }
168
- invalidateSubjectsCache();
169
189
  const schoolId = ((_f = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _f === void 0 ? void 0 : _f.id) || "";
170
190
  fetch(`${SUBJECT_API_ROUTES.LIST}?currentPage=1&pageLimit=${pageLimit}&schoolId=${schoolId}`, {
171
191
  headers: {
@@ -191,17 +211,20 @@ export const SubjectMoreActions = () => {
191
211
  else {
192
212
  const r = status.results;
193
213
  const detail = ((_g = r === null || r === void 0 ? void 0 : r.errors) === null || _g === void 0 ? void 0 : _g.length)
194
- ? formatErrorSummary(r.errors)
195
- : "Unknown error";
196
- showErrorToast(`Bulk ${label} failed.\n${detail}`);
214
+ ? formatErrorSummary(t, r.errors)
215
+ : t("unknownError");
216
+ showErrorToast(`${t("messagesBulkFailed", { action: label })}\n${detail}`);
197
217
  }
198
218
  }
199
219
  catch (error) {
200
220
  if (error.message === "Polling cancelled")
201
221
  return;
202
- showErrorToast(`Bulk ${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
222
+ showErrorToast(t("messagesBulkFailedDetail", {
223
+ action: label,
224
+ error: error instanceof Error ? error.message : t("unknownError"),
225
+ }));
203
226
  }
204
- }, [dispatch]);
227
+ }, [dispatch, t]);
205
228
  const handleBulkCreate = useCallback((files) => handleBulkFlow(files, "POST"), [handleBulkFlow]);
206
229
  const handleBulkUpdate = useCallback((files) => handleBulkFlow(files, "PUT"), [handleBulkFlow]);
207
230
  const create = [
@@ -15,17 +15,11 @@ export declare const TEACHER_API_ROUTES: {
15
15
  readonly BULK_STATUS: (jobId: string) => string;
16
16
  };
17
17
  export declare const GENDER_OPTIONS: readonly [{
18
- readonly label: "Select Gender";
19
- readonly value: "";
20
- }, {
21
18
  readonly label: "Male";
22
19
  readonly value: "MALE";
23
20
  }, {
24
21
  readonly label: "Female";
25
22
  readonly value: "FEMALE";
26
- }, {
27
- readonly label: "Other";
28
- readonly value: "OTHER";
29
23
  }, {
30
24
  readonly label: "Prefer not to say";
31
25
  readonly value: "PREFER_NOT_TO_SAY";
@@ -24,10 +24,8 @@ export const TEACHER_API_ROUTES = {
24
24
  // GENDER OPTIONS
25
25
  // ============================================================================
26
26
  export const GENDER_OPTIONS = [
27
- { label: "Select Gender", value: "" },
28
27
  { label: "Male", value: "MALE" },
29
28
  { label: "Female", value: "FEMALE" },
30
- { label: "Other", value: "OTHER" },
31
29
  { label: "Prefer not to say", value: "PREFER_NOT_TO_SAY" },
32
30
  ];
33
31
  // ============================================================================
@@ -214,6 +214,7 @@ export declare const useTeacherModule: () => {
214
214
  }[];
215
215
  listFetchNow: (url?: string, config?: import("@react-pakistan/util-functions/hooks/use-fetch").FetchConfig) => void;
216
216
  listLoading: boolean;
217
+ resetRecordFormState: () => void;
217
218
  rowActions: RowAction[];
218
219
  toggleStatus: (row?: TableRow) => void;
219
220
  updateLoading: boolean;
@@ -9,7 +9,7 @@
9
9
  * Key responsibilities:
10
10
  * - Expose `useTeacherModule()` which UI components call for actions
11
11
  * - Keep module-specific `apiParams` and callbacks in one place
12
- * - Ensure cache invalidation and toast notifications on mutation
12
+ * - Ensure toast notifications on mutation
13
13
  *
14
14
  * Exported utilities:
15
15
  * - `TeacherProvider` — provider component used by the page
@@ -29,7 +29,6 @@ import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-ut
29
29
  import { GENDER } from "../../type";
30
30
  import { TEACHER_API_ROUTES, pageLimit } from "./constants";
31
31
  import { teacherFormValidation } from "./validate";
32
- import { getCachedTeachers, invalidateTeachersCache } from "./cache";
33
32
  import { getCachedWorkspaceSync } from "../workspace/cache";
34
33
  import { processAvatarFile } from "./avatar-upload";
35
34
  // ============================================================================
@@ -146,10 +145,6 @@ export const useTeacherModule = () => {
146
145
  }), [state, schoolId]);
147
146
  const byIdParams = useMemo(() => ({ id: state.id }), [state.id]);
148
147
  const deleteParams = useMemo(() => ({ id: state.id }), [state.id]);
149
- const isDefaultListState = state.currentPage === 1 &&
150
- state.pageLimit === pageLimit &&
151
- !debouncedQuery &&
152
- state.filterEnabled === undefined;
153
148
  // ============================================================================
154
149
  // 1.4.3 UTILITIES
155
150
  // ============================================================================
@@ -175,6 +170,46 @@ export const useTeacherModule = () => {
175
170
  payload: { drawer: null },
176
171
  });
177
172
  }, [dispatch]);
173
+ const resetRecordFormState = useCallback(() => {
174
+ dispatch({
175
+ type: TEACHER_ACTION_TYPES.SET_ERRORS,
176
+ payload: { errors: {} },
177
+ });
178
+ dispatch({
179
+ type: TEACHER_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
180
+ payload: { disabled: false },
181
+ });
182
+ dispatch({
183
+ type: TEACHER_ACTION_TYPES.SET_FORM_DATA,
184
+ payload: {
185
+ form: {
186
+ address: null,
187
+ avatar: null,
188
+ bio: null,
189
+ city: null,
190
+ country: null,
191
+ dateOfBirth: null,
192
+ emergencyPhone: "",
193
+ enabled: true,
194
+ experience: null,
195
+ filterEnabled: undefined,
196
+ firstName: "",
197
+ gender: GENDER.MALE,
198
+ id: "",
199
+ joiningDate: new Date().toISOString().split("T")[0],
200
+ lastName: "",
201
+ phone: "",
202
+ postalCode: null,
203
+ qualification: null,
204
+ schoolId,
205
+ specialization: null,
206
+ state: null,
207
+ teacherCode: "",
208
+ userId: null,
209
+ },
210
+ },
211
+ });
212
+ }, [dispatch, schoolId]);
178
213
  // ============================================================================
179
214
  // 1.4.4 API CALLBACKS
180
215
  // ============================================================================
@@ -200,7 +235,6 @@ export const useTeacherModule = () => {
200
235
  }
201
236
  if (data) {
202
237
  const isCreated = isCreatedOrUpdated(data);
203
- invalidateTeachersCache();
204
238
  showToast(isCreated ? t("messagesCreateSuccess") : t("messagesSaveSuccess"), TOAST_VARIANT.SUCCESS);
205
239
  resetFormAndCloseDrawer();
206
240
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
@@ -228,7 +262,6 @@ export const useTeacherModule = () => {
228
262
  return;
229
263
  }
230
264
  if (data) {
231
- invalidateTeachersCache();
232
265
  showToast(t("messagesDeleteSuccess"), TOAST_VARIANT.SUCCESS);
233
266
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
234
267
  }
@@ -281,21 +314,23 @@ export const useTeacherModule = () => {
281
314
  });
282
315
  }, [dispatch]);
283
316
  const handleView = useCallback((row) => {
317
+ resetRecordFormState();
284
318
  byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
285
319
  dispatch({
286
320
  type: TEACHER_ACTION_TYPES.SET_DRAWER,
287
321
  payload: { drawer: TEACHER_DRAWER.VIEW_DRAWER },
288
322
  });
289
- }, [byIdFetchNow, dispatch]);
323
+ }, [byIdFetchNow, dispatch, resetRecordFormState]);
290
324
  const handleEdit = useCallback((row) => {
325
+ resetRecordFormState();
291
326
  byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
292
327
  dispatch({
293
328
  type: TEACHER_ACTION_TYPES.SET_DRAWER,
294
329
  payload: { drawer: TEACHER_DRAWER.FORM_DRAWER },
295
330
  });
296
- }, [byIdFetchNow, dispatch]);
331
+ }, [byIdFetchNow, dispatch, resetRecordFormState]);
297
332
  const handleDelete = useCallback((row) => {
298
- if (!confirm(t("actionDeleteConfirmation")))
333
+ if (!confirm(t("messagesDeleteConfirmation")))
299
334
  return;
300
335
  deleteFetchNow === null || deleteFetchNow === void 0 ? void 0 : deleteFetchNow(undefined, {
301
336
  body: JSON.stringify({ id: row === null || row === void 0 ? void 0 : row.id }),
@@ -479,19 +514,19 @@ export const useTeacherModule = () => {
479
514
  {
480
515
  enabled: true,
481
516
  handleOnClick: handleMoreActions,
482
- label: t("actionHeaderMoreActions"),
517
+ label: t("actionsButtonMoreActions"),
483
518
  order: 0,
484
519
  },
485
520
  {
486
521
  enabled: true,
487
522
  handleOnClick: handleFilters,
488
- label: t("actionHeaderFilters"),
523
+ label: t("actionsButtonFilters"),
489
524
  order: 1,
490
525
  },
491
526
  {
492
527
  enabled: true,
493
528
  handleOnClick: handleCreate,
494
- label: t("actionHeaderAdd"),
529
+ label: t("actionsButtonAdd"),
495
530
  order: 2,
496
531
  },
497
532
  ], [handleCreate, handleFilters, handleMoreActions, t]);
@@ -499,58 +534,41 @@ export const useTeacherModule = () => {
499
534
  {
500
535
  enabled: true,
501
536
  handleOnClick: handleView,
502
- label: t("actionRowView"),
537
+ label: t("actionsButtonView"),
503
538
  order: 1,
504
539
  },
505
540
  {
506
541
  enabled: (row) => (row === null || row === void 0 ? void 0 : row.enabled) === true,
507
542
  handleOnClick: handleEdit,
508
- label: t("actionRowEdit"),
543
+ label: t("actionsButtonEdit"),
509
544
  order: 2,
510
545
  },
511
546
  {
512
547
  enabled: (row) => (row === null || row === void 0 ? void 0 : row.enabled) === false,
513
548
  handleOnClick: handleDelete,
514
- label: t("actionRowDelete"),
549
+ label: t("actionsButtonDelete"),
515
550
  order: 3,
516
551
  variant: "destructive",
517
552
  },
518
553
  {
519
554
  enabled: false,
520
555
  handleOnClick: toggleStatus,
521
- label: t("actionRowToggleStatus"),
556
+ label: t("actionsButtonToggleStatus"),
522
557
  order: 4,
523
558
  },
524
559
  ], [handleDelete, handleEdit, handleView, t, toggleStatus]);
525
560
  // ============================================================================
526
561
  // 1.4.9 EFFECTS
527
562
  // ============================================================================
563
+ useEffect(() => {
564
+ listFetchNowRef.current = listFetchNow;
565
+ }, [listFetchNow]);
528
566
  useEffect(() => {
529
567
  var _a;
530
568
  if (!schoolId)
531
569
  return;
532
- if (isDefaultListState) {
533
- (async () => {
534
- try {
535
- const { count, items } = await getCachedTeachers({
536
- params: listParams,
537
- });
538
- dispatch({
539
- type: TEACHER_ACTION_TYPES.SET_ITEMS,
540
- payload: { items: items || [], count: count || 0 },
541
- });
542
- }
543
- catch (_a) {
544
- showToast(t("messagesFetchFailed"), TOAST_VARIANT.ERROR);
545
- }
546
- })();
547
- return;
548
- }
549
570
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
550
- }, [dispatch, isDefaultListState, listParams, schoolId, showToast, t]);
551
- useEffect(() => {
552
- listFetchNowRef.current = listFetchNow;
553
- }, [listFetchNow]);
571
+ }, [dispatch, listParams, schoolId, showToast, t]);
554
572
  // ============================================================================
555
573
  // 1.4.10 RETURN
556
574
  // ============================================================================
@@ -576,6 +594,7 @@ export const useTeacherModule = () => {
576
594
  headerActions,
577
595
  listFetchNow,
578
596
  listLoading,
597
+ resetRecordFormState,
579
598
  rowActions,
580
599
  toggleStatus,
581
600
  updateLoading });
@@ -3,27 +3,62 @@ 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
5
  import { EnhancedDropzone } from "@appcorp/shadcn/components/enhanced-dropzone";
6
- import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
6
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
7
7
  import { EnhancedTextarea } from "@appcorp/shadcn/components/enhanced-textarea";
8
8
  import { useTranslations } from "next-intl";
9
- import { USER_ROLE } from "../../type";
10
- import { getCachedUsersSync } from "../user/cache";
11
9
  import { useTeacherModule } from "./context";
12
- import { GENDER_OPTIONS } from "./constants";
10
+ import { GENDER_OPTIONS, TEACHER_API_ROUTES } from "./constants";
13
11
  export const TeacherForm = () => {
14
- var _a;
15
12
  const t = useTranslations("teacher");
13
+ const genderLabelMap = {
14
+ MALE: t("formOptionMale"),
15
+ FEMALE: t("formOptionFemale"),
16
+ PREFER_NOT_TO_SAY: t("formOptionPreferNotToSay"),
17
+ };
16
18
  const context = useTeacherModule();
17
- const { address, avatar, bio, city, country, dateOfBirth, emergencyPhone, enabled, errors, experience, firstName, gender, joiningDate, lastName, phone, postalCode, qualification, specialization, userId, } = context.state;
19
+ const { address, avatar, bio, city, country, dateOfBirth, emergencyPhone, enabled, errors, experience, firstName, gender, joiningDate, lastName, phone, postalCode, qualification, specialization,
20
+ // userId,
21
+ } = context.state;
18
22
  const stateValue = context.state.state;
19
23
  const { handleChange, handleAvatarUpload } = context;
20
- const users = getCachedUsersSync();
24
+ // const users = getCachedUsersSync();
25
+ // const { enhancedComboboxElement: userIdCombo } = useEnhancedCombobox({
26
+ // emptyText: t("formNoTeacherUsersEmpty"),
27
+ // id: "userId",
28
+ // info: t("formLinkedSystemUserLabel"),
29
+ // label: t("formTeacherUserPlaceholder"),
30
+ // onValueChange: (value) => handleChange("userId", value || null),
31
+ // options: users.items
32
+ // ?.filter(
33
+ // ({ workspaces }) =>
34
+ // workspaces?.filter(({ role }) => role?.userRole === USER_ROLE.TEACHER)
35
+ // .length
36
+ // )
37
+ // .map((u) => ({
38
+ // id: u.id,
39
+ // name: u?.name || u?.email || u.id,
40
+ // })),
41
+ // placeholder: t("formSelectLinkedUserPlaceholder"),
42
+ // searchEndpoint: TEACHER_API_ROUTES.UNIT,
43
+ // searchPlaceholder: t("formSearchUsersPlaceholder"),
44
+ // value: userId || "",
45
+ // });
46
+ const { enhancedComboboxElement: genderCombo } = useEnhancedCombobox({
47
+ id: "gender",
48
+ info: t("formTeachersGenderPlaceholder"),
49
+ label: t("formGenderLabel"),
50
+ onValueChange: (value) => handleChange("gender", value || null),
51
+ options: GENDER_OPTIONS.map((opt) => ({
52
+ id: opt.value,
53
+ name: genderLabelMap[opt.value] || opt.label,
54
+ })),
55
+ placeholder: "",
56
+ searchEndpoint: TEACHER_API_ROUTES.UNIT,
57
+ value: gender || "",
58
+ });
21
59
  return (_jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedInput, { error: errors.firstName, id: "firstName", info: t("formTeachersFirstNameLabel"), label: t("formFirstNameLabel"), onChange: (e) => handleChange("firstName", e.target.value), placeholder: t("formFirstNamePlaceholder"), required: true, value: firstName }), _jsx(EnhancedInput, { error: errors.lastName, id: "lastName", info: t("formTeachersLastNameLabel"), label: t("formLastNameLabel"), onChange: (e) => handleChange("lastName", e.target.value), placeholder: t("formLastNamePlaceholder"), required: true, value: lastName }), _jsx(EnhancedInput, { error: errors.joiningDate, id: "joiningDate", info: t("formDateTeacherJoinedTheSchoolLabel"), label: t("formJoiningDateLabel"), onChange: (e) => handleChange("joiningDate", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, required: true, type: "date", value: joiningDate
22
60
  ? new Date(joiningDate).toISOString().split("formTLabel")[0]
23
- : "" }), _jsx(EnhancedCombobox, { emptyText: t("formNoTeacherUsersEmpty"), error: errors.userId, id: "userId", info: t("formLinkedSystemUserLabel"), label: t("formTeacherUserPlaceholder"), onValueChange: (value) => handleChange("userId", value || null), options: (_a = users.items) === null || _a === void 0 ? void 0 : _a.filter(({ workspaces }) => workspaces === null || workspaces === void 0 ? void 0 : workspaces.filter(({ role }) => (role === null || role === void 0 ? void 0 : role.userRole) === USER_ROLE.TEACHER).length).map((u) => ({
24
- label: (u === null || u === void 0 ? void 0 : u.name) || (u === null || u === void 0 ? void 0 : u.email) || u.id,
25
- value: u.id,
26
- })), placeholder: t("formSelectLinkedUserPlaceholder"), searchPlaceholder: t("formSearchUsersPlaceholder"), value: userId || "" }), _jsx(EnhancedCombobox, { error: errors.gender, id: "gender", info: t("formTeachersGenderPlaceholder"), label: t("formGenderLabel"), onValueChange: (value) => handleChange("gender", value || null), options: GENDER_OPTIONS.slice(1), value: gender || "" }), _jsx(EnhancedInput, { error: errors.dateOfBirth, id: "dateOfBirth", info: t("formTeachersDateOfBirthLabel"), label: t("formDateOfBirthLabel"), onChange: (e) => handleChange("dateOfBirth", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, type: "date", value: dateOfBirth
61
+ : "" }), genderCombo, _jsx(EnhancedInput, { error: errors.dateOfBirth, id: "dateOfBirth", info: t("formTeachersDateOfBirthLabel"), label: t("formDateOfBirthLabel"), onChange: (e) => handleChange("dateOfBirth", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, type: "date", value: dateOfBirth
27
62
  ? new Date(dateOfBirth).toISOString().split("formTLabel")[0]
28
63
  : "" }), _jsx(EnhancedInput, { error: errors.phone, id: "phone", info: t("formTeachersPhoneNumberLabel"), label: t("formPhoneLabel"), onChange: (e) => handleChange("phone", e.target.value), placeholder: t("formPhonePlaceholder"), type: "tel", value: phone || "" }), _jsx(EnhancedInput, { error: errors.emergencyPhone, id: "emergencyPhone", info: t("formEmergencyContactNumberLabel"), label: t("formEmergencyPhoneLabel"), onChange: (e) => handleChange("emergencyPhone", e.target.value), placeholder: t("formEmergencyPhonePlaceholder"), type: "tel", value: emergencyPhone || "" }), _jsx(EnhancedInput, { error: errors.qualification, id: "qualification", info: t("formTeachersEducationalQualificationLabel"), label: t("formQualificationLabel"), onChange: (e) => handleChange("qualification", e.target.value), placeholder: t("formQualificationPlaceholder"), value: qualification || "" }), _jsx(EnhancedInput, { error: errors.specialization, id: "specialization", info: t("formTeachersAreaOfSpecializationLabel"), label: t("formSpecializationLabel"), onChange: (e) => handleChange("specialization", e.target.value), placeholder: t("formSpecializationPlaceholder"), value: specialization || "" }), _jsx(EnhancedInput, { error: errors.experience, id: "experience", info: t("formYearsOfTeachingExperienceLabel"), label: t("formExperienceLabel"), onChange: (e) => handleChange("experience", parseInt(e.target.value) || null), placeholder: t("formExperiencePlaceholder"), type: "number", value: (experience === null || experience === void 0 ? void 0 : experience.toString()) || "" }), _jsx(EnhancedInput, { error: errors.address, id: "address", info: t("formStreetAddressLabel"), label: t("formAddressLabel"), onChange: (e) => handleChange("address", e.target.value), placeholder: t("formAddressPlaceholder"), value: address || "" }), _jsx(EnhancedInput, { error: errors.city, id: "city", info: t("formCityNameLabel"), label: t("formCityLabel"), onChange: (e) => handleChange("city", e.target.value), placeholder: t("formCityPlaceholder"), value: city || "" }), _jsx(EnhancedInput, { error: errors.state, id: "state", info: t("formStateOrProvinceLabel"), label: t("formStateProvinceLabel"), onChange: (e) => handleChange("state", e.target.value), placeholder: t("formStatePlaceholder"), value: stateValue || "" }), _jsx(EnhancedInput, { error: errors.country, id: "country", info: t("formCountryNameLabel"), label: t("formCountryLabel"), onChange: (e) => handleChange("country", e.target.value), placeholder: t("formCountryPlaceholder"), value: country || "" }), _jsx(EnhancedInput, { error: errors.postalCode, id: "postalCode", info: t("formPostalOrZipCodeLabel"), label: t("formPostalCodeLabel"), onChange: (e) => handleChange("postalCode", e.target.value), placeholder: t("formPostalCodePlaceholder"), value: postalCode || "" }), _jsx(EnhancedDropzone, { accept: ["image/*"], error: errors.avatar, id: "avatar", info: t("formUploadTeachersPhoto"), label: t("formAvatarLabel"), maxFiles: 1, maxSize: 100 * 1024, onChange: (files) => {
29
64
  if (files.length > 0) {
@@ -8,7 +8,6 @@ import { Timeline } from "../../components/timeline";
8
8
  import { useTranslations } from "next-intl";
9
9
  import { teacherFormValidation } from "./validate";
10
10
  import { TEACHER_API_ROUTES, pageLimit } from "./constants";
11
- import { invalidateTeachersCache } from "./cache";
12
11
  import { TEACHER_ACTION_TYPES, useTeacherContext } from "./context";
13
12
  import { useRef, useEffect, useCallback } from "react";
14
13
  const workspace = getCachedWorkspaceSync();
@@ -77,13 +76,15 @@ async function pollBulkJob(jobId, signal, onProgress) {
77
76
  }
78
77
  }
79
78
  }
80
- function formatErrorSummary(errors) {
79
+ function formatErrorSummary(t, errors) {
81
80
  if (!(errors === null || errors === void 0 ? void 0 : errors.length))
82
81
  return "";
83
- const lines = errors.slice(0, 5).map((e) => `Row ${e.row}: ${e.error}`);
82
+ const lines = errors
83
+ .slice(0, 5)
84
+ .map((e) => t("messagesBulkRowError", { row: e.row, error: e.error }));
84
85
  const remaining = errors.length - 5;
85
86
  if (remaining > 0)
86
- lines.push(`...and ${remaining} more`);
87
+ lines.push(t("messagesBulkMoreRows", { count: remaining }));
87
88
  return lines.join("\n");
88
89
  }
89
90
  export const TeacherMoreActions = () => {
@@ -116,7 +117,7 @@ export const TeacherMoreActions = () => {
116
117
  const text = await file.text();
117
118
  const records = converter.csv2json(text);
118
119
  if (!Array.isArray(records) || records.length === 0) {
119
- showErrorToast("CSV file is empty or invalid");
120
+ showErrorToast(t("messagesBulkCsvEmpty"));
120
121
  return;
121
122
  }
122
123
  // Client-side validation — fast feedback, saves worker round-trip
@@ -132,7 +133,7 @@ export const TeacherMoreActions = () => {
132
133
  }
133
134
  else {
134
135
  if (!((_b = row.id) === null || _b === void 0 ? void 0 : _b.trim()))
135
- msgs.push("id is required for update");
136
+ msgs.push(t("validationRequiredIdForUpdate"));
136
137
  }
137
138
  if (msgs.length > 0) {
138
139
  validationErrors.push({ row: i + 1, messages: msgs });
@@ -141,40 +142,59 @@ export const TeacherMoreActions = () => {
141
142
  if (validationErrors.length > 0) {
142
143
  const summary = validationErrors
143
144
  .slice(0, 5)
144
- .map((e) => `Row ${e.row}: ${e.messages.join("; ")}`)
145
+ .map((e) => t("messagesBulkRowError", {
146
+ row: e.row,
147
+ error: e.messages.join("; "),
148
+ }))
145
149
  .join("\n");
146
- const remaining = validationErrors.length - 5;
147
- showErrorToast(`Validation failed for ${validationErrors.length} row(s).\n${summary}${remaining > 0 ? `\n...and ${remaining} more` : ""}`);
150
+ showErrorToast(t("messagesBulkValidationFailed", {
151
+ count: validationErrors.length,
152
+ errors: summary,
153
+ }));
148
154
  return;
149
155
  }
150
156
  try {
151
- showInfoToast(`Bulk ${label} job queued (${records.length} records). Processing...`);
157
+ showInfoToast(t("messagesBulkJobQueued", { action: label, count: records.length }));
152
158
  let jobId;
153
159
  try {
154
160
  jobId = await submitBulkJob(text, method, signal);
155
161
  }
156
162
  catch (submitError) {
157
- showErrorToast(`Failed to submit ${label} job: ${submitError instanceof Error ? submitError.message : "Unknown error"}`);
163
+ showErrorToast(t("messagesBulkJobSubmitFailed", {
164
+ action: label,
165
+ error: submitError instanceof Error
166
+ ? submitError.message
167
+ : t("unknownError"),
168
+ }));
158
169
  return;
159
170
  }
160
171
  const status = await pollBulkJob(jobId, signal, (processed, total) => {
161
- showInfoToast(`Processing ${processed}/${total} teachers...`);
172
+ showInfoToast(t("messagesBulkProgress", { processed, total }));
162
173
  });
163
174
  if (signal.aborted)
164
175
  return;
165
176
  if (status.status === "completed") {
166
177
  const r = status.results;
167
178
  if (r && ((_c = r.errors) === null || _c === void 0 ? void 0 : _c.length) > 0) {
168
- const summary = formatErrorSummary(r.errors);
169
- showSuccessToast(`Created ${r.created} | Updated ${r.updated} | Skipped ${r.skipped}\n${summary}`);
179
+ const summary = formatErrorSummary(t, r.errors);
180
+ showSuccessToast(t("messagesBulkResults", {
181
+ created: r.created,
182
+ updated: r.updated,
183
+ skipped: r.skipped,
184
+ }) +
185
+ "\n" +
186
+ summary);
170
187
  }
171
188
  else if (r) {
172
- showSuccessToast(`Created ${r.created} | Updated ${r.updated} | Skipped ${r.skipped}`);
189
+ showSuccessToast(t("messagesBulkResults", {
190
+ created: r.created,
191
+ updated: r.updated,
192
+ skipped: r.skipped,
193
+ }));
173
194
  }
174
195
  else {
175
- showSuccessToast("Bulk operation completed successfully");
196
+ showSuccessToast(t("messagesBulkSuccess"));
176
197
  }
177
- invalidateTeachersCache();
178
198
  const schoolId = ((_d = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _d === void 0 ? void 0 : _d.id) || "";
179
199
  fetch(`${TEACHER_API_ROUTES.LIST}?currentPage=1&pageLimit=${pageLimit}&schoolId=${schoolId}`, {
180
200
  headers: {
@@ -201,17 +221,20 @@ export const TeacherMoreActions = () => {
201
221
  else {
202
222
  const r = status.results;
203
223
  const detail = ((_e = r === null || r === void 0 ? void 0 : r.errors) === null || _e === void 0 ? void 0 : _e.length)
204
- ? formatErrorSummary(r.errors)
205
- : "Unknown error";
206
- showErrorToast(`Bulk ${label} failed.\n${detail}`);
224
+ ? formatErrorSummary(t, r.errors)
225
+ : t("unknownError");
226
+ showErrorToast(t("messagesBulkFailed", { action: label }) + "\n" + detail);
207
227
  }
208
228
  }
209
229
  catch (error) {
210
230
  if (error.message === "Polling cancelled")
211
231
  return;
212
- showErrorToast(`Bulk ${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
232
+ showErrorToast(t("messagesBulkFailedDetail", {
233
+ action: label,
234
+ error: error instanceof Error ? error.message : t("unknownError"),
235
+ }));
213
236
  }
214
- }, [dispatch]);
237
+ }, [dispatch, t]);
215
238
  const handleBulkCreate = useCallback((files) => handleBulkFlow(files, "POST"), [handleBulkFlow]);
216
239
  const handleBulkUpdate = useCallback((files) => handleBulkFlow(files, "PUT"), [handleBulkFlow]);
217
240
  const create = [
@@ -27,6 +27,7 @@ export declare const useUserModule: () => {
27
27
  }[];
28
28
  listFetchNow: (url?: string, config?: import("@react-pakistan/util-functions/hooks/use-fetch").FetchConfig) => void;
29
29
  listLoading: boolean;
30
+ resetRecordFormState: () => void;
30
31
  rowActions: RowAction[];
31
32
  toggleStatus: (row?: TableRow) => void;
32
33
  updateLoading: boolean;