@appcorp/fusion-storybook 0.2.40 → 0.2.42

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 (98) 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/form.js +26 -10
  8. package/base-modules/attendance/more-actions.js +34 -25
  9. package/base-modules/campus/context.js +7 -7
  10. package/base-modules/class/cache.js +0 -1
  11. package/base-modules/class/context.js +10 -48
  12. package/base-modules/class/more-actions.js +0 -2
  13. package/base-modules/course/context.js +3 -37
  14. package/base-modules/course/form.js +91 -292
  15. package/base-modules/discount-code/constants.d.ts +5 -0
  16. package/base-modules/discount-code/constants.js +5 -0
  17. package/base-modules/discount-code/context.d.ts +1 -0
  18. package/base-modules/discount-code/context.js +40 -39
  19. package/base-modules/discount-code/form.js +21 -15
  20. package/base-modules/discount-code/more-actions.js +1 -1
  21. package/base-modules/enrollment/context.js +3 -37
  22. package/base-modules/enrollment/form.js +38 -11
  23. package/base-modules/enrollment/more-actions.js +0 -2
  24. package/base-modules/expense/constants.js +1 -1
  25. package/base-modules/expense/context.js +5 -32
  26. package/base-modules/expense/filter.js +50 -3
  27. package/base-modules/expense/form.js +82 -6
  28. package/base-modules/family/context.js +7 -38
  29. package/base-modules/family-member/context.js +7 -39
  30. package/base-modules/fee-structure/context.js +1 -25
  31. package/base-modules/fee-structure/form.js +77 -89
  32. package/base-modules/fee-structure/more-actions.js +0 -2
  33. package/base-modules/rbac/context.d.ts +1 -0
  34. package/base-modules/rbac/context.js +23 -32
  35. package/base-modules/school/context.js +1 -1
  36. package/base-modules/school/form.js +34 -14
  37. package/base-modules/section/context.d.ts +1 -0
  38. package/base-modules/section/context.js +40 -47
  39. package/base-modules/section/form.js +25 -80
  40. package/base-modules/section/more-actions.js +0 -2
  41. package/base-modules/section/view.js +9 -7
  42. package/base-modules/student-fee/context/use-student-fee-module.d.ts +1 -0
  43. package/base-modules/student-fee/context/use-student-fee-module.js +48 -32
  44. package/base-modules/student-fee/context.d.ts +1 -1
  45. package/base-modules/student-fee/context.js +1 -1
  46. package/base-modules/student-fee/filter.js +23 -3
  47. package/base-modules/student-fee/form.js +93 -174
  48. package/base-modules/student-fee/view.d.ts +7 -1
  49. package/base-modules/student-fee/view.js +17 -20
  50. package/base-modules/student-profile/constants.d.ts +0 -6
  51. package/base-modules/student-profile/constants.js +1 -3
  52. package/base-modules/student-profile/context/use-student-profile-module.d.ts +1 -0
  53. package/base-modules/student-profile/context/use-student-profile-module.js +62 -55
  54. package/base-modules/student-profile/context.d.ts +1 -1
  55. package/base-modules/student-profile/context.js +1 -1
  56. package/base-modules/student-profile/filter.js +23 -3
  57. package/base-modules/student-profile/form.js +35 -3
  58. package/base-modules/subject/context.d.ts +1 -0
  59. package/base-modules/subject/context.js +38 -47
  60. package/base-modules/subject/more-actions.js +0 -2
  61. package/base-modules/teacher/constants.d.ts +0 -6
  62. package/base-modules/teacher/constants.js +0 -2
  63. package/base-modules/teacher/context.d.ts +1 -0
  64. package/base-modules/teacher/context.js +58 -39
  65. package/base-modules/teacher/form.js +46 -11
  66. package/base-modules/teacher/more-actions.js +0 -2
  67. package/base-modules/user/context/use-user-module.d.ts +1 -0
  68. package/base-modules/user/context/use-user-module.js +36 -32
  69. package/base-modules/user/context.js +1 -1
  70. package/base-modules/user/filter.js +6 -4
  71. package/base-modules/user/form.js +29 -5
  72. package/base-modules/user/more-actions.js +9 -7
  73. package/base-modules/user/view.js +3 -1
  74. package/base-modules/workspace/form.js +18 -8
  75. package/base-modules/workspace-user/context.d.ts +2 -1
  76. package/base-modules/workspace-user/context.js +31 -29
  77. package/package.json +1 -1
  78. package/tsconfig.build.tsbuildinfo +1 -1
  79. package/base-modules/admission/cache.d.ts +0 -14
  80. package/base-modules/admission/cache.js +0 -31
  81. package/base-modules/attendance/cache.d.ts +0 -14
  82. package/base-modules/attendance/cache.js +0 -31
  83. package/base-modules/course/cache.d.ts +0 -14
  84. package/base-modules/course/cache.js +0 -31
  85. package/base-modules/enrollment/cache.d.ts +0 -14
  86. package/base-modules/enrollment/cache.js +0 -31
  87. package/base-modules/expense/cache.d.ts +0 -14
  88. package/base-modules/expense/cache.js +0 -31
  89. package/base-modules/family/cache.d.ts +0 -14
  90. package/base-modules/family/cache.js +0 -31
  91. package/base-modules/family-member/cache.d.ts +0 -14
  92. package/base-modules/family-member/cache.js +0 -31
  93. package/base-modules/rbac/cache.d.ts +0 -27
  94. package/base-modules/rbac/cache.js +0 -46
  95. package/base-modules/student-fee/cache.d.ts +0 -15
  96. package/base-modules/student-fee/cache.js +0 -21
  97. package/base-modules/workspace-user/cache.d.ts +0 -14
  98. package/base-modules/workspace-user/cache.js +0 -31
@@ -9,7 +9,7 @@
9
9
  * Key responsibilities:
10
10
  * - Expose `useSectionModule()` 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
  * - `SectionProvider` — provider component used by the page
@@ -28,7 +28,6 @@ import { DRAWER_TYPES } from "@react-pakistan/util-functions/factory/generic-com
28
28
  import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
29
29
  import { SECTION_API_ROUTES, pageLimit } from "./constants";
30
30
  import { sectionFormValidation } from "./validate";
31
- import { getCachedSections, invalidateSectionsCache } from "./cache";
32
31
  import { getCachedWorkspaceSync } from "../workspace/cache";
33
32
  // ============================================================================
34
33
  // 1.1 DRAWER TYPES
@@ -136,6 +135,30 @@ export const useSectionModule = () => {
136
135
  payload: { drawer: null },
137
136
  });
138
137
  }, [dispatch]);
138
+ const resetRecordFormState = useCallback(() => {
139
+ dispatch({
140
+ type: SECTION_ACTION_TYPES.SET_ERRORS,
141
+ payload: { errors: {} },
142
+ });
143
+ dispatch({
144
+ type: SECTION_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
145
+ payload: { disabled: false },
146
+ });
147
+ dispatch({
148
+ type: SECTION_ACTION_TYPES.SET_FORM_DATA,
149
+ payload: {
150
+ form: {
151
+ capacity: null,
152
+ classId: "",
153
+ enabled: true,
154
+ filterEnabled: undefined,
155
+ id: "",
156
+ name: "",
157
+ schoolId,
158
+ },
159
+ },
160
+ });
161
+ }, [dispatch, schoolId]);
139
162
  // ============================================================================
140
163
  // 1.4.4 API CALLBACKS
141
164
  // ============================================================================
@@ -161,7 +184,6 @@ export const useSectionModule = () => {
161
184
  }
162
185
  if (data) {
163
186
  const isCreated = isCreatedOrUpdated(data);
164
- invalidateSectionsCache();
165
187
  showToast(isCreated ? t("messagesCreateSuccess") : t("messagesSaveSuccess"), TOAST_VARIANT.SUCCESS);
166
188
  resetFormAndCloseDrawer();
167
189
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
@@ -186,7 +208,6 @@ export const useSectionModule = () => {
186
208
  return;
187
209
  }
188
210
  if (data) {
189
- invalidateSectionsCache();
190
211
  showToast(t("messagesDeleteSuccess"), TOAST_VARIANT.SUCCESS);
191
212
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
192
213
  }
@@ -239,19 +260,21 @@ export const useSectionModule = () => {
239
260
  });
240
261
  }, [dispatch]);
241
262
  const handleView = useCallback((row) => {
263
+ resetRecordFormState();
242
264
  byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
243
265
  dispatch({
244
266
  type: SECTION_ACTION_TYPES.SET_DRAWER,
245
267
  payload: { drawer: SECTION_DRAWER.VIEW_DRAWER },
246
268
  });
247
- }, [byIdFetchNow, dispatch]);
269
+ }, [byIdFetchNow, dispatch, resetRecordFormState]);
248
270
  const handleEdit = useCallback((row) => {
271
+ resetRecordFormState();
249
272
  byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
250
273
  dispatch({
251
274
  type: SECTION_ACTION_TYPES.SET_DRAWER,
252
275
  payload: { drawer: SECTION_DRAWER.FORM_DRAWER },
253
276
  });
254
- }, [byIdFetchNow, dispatch]);
277
+ }, [byIdFetchNow, dispatch, resetRecordFormState]);
255
278
  const handleDelete = useCallback((row) => {
256
279
  if (!confirm(t("messagesDeleteConfirmation")))
257
280
  return;
@@ -372,19 +395,19 @@ export const useSectionModule = () => {
372
395
  {
373
396
  enabled: true,
374
397
  handleOnClick: handleMoreActions,
375
- label: t("actionHeaderMoreActions"),
398
+ label: t("actionsButtonMoreActions"),
376
399
  order: 0,
377
400
  },
378
401
  {
379
402
  enabled: true,
380
403
  handleOnClick: handleFilters,
381
- label: t("actionHeaderFilters"),
404
+ label: t("actionsButtonFilters"),
382
405
  order: 1,
383
406
  },
384
407
  {
385
408
  enabled: true,
386
409
  handleOnClick: handleCreate,
387
- label: t("actionHeaderAdd"),
410
+ label: t("actionsButtonAdd"),
388
411
  order: 2,
389
412
  },
390
413
  ], [handleCreate, handleFilters, handleMoreActions, t]);
@@ -392,70 +415,39 @@ export const useSectionModule = () => {
392
415
  {
393
416
  enabled: true,
394
417
  handleOnClick: handleView,
395
- label: t("actionRowView"),
418
+ label: t("actionsButtonView"),
396
419
  order: 1,
397
420
  },
398
421
  {
399
422
  enabled: (row) => (row === null || row === void 0 ? void 0 : row.enabled) === true,
400
423
  handleOnClick: handleEdit,
401
- label: t("actionRowEdit"),
424
+ label: t("actionsButtonEdit"),
402
425
  order: 2,
403
426
  },
404
427
  {
405
428
  enabled: (row) => (row === null || row === void 0 ? void 0 : row.enabled) === false,
406
429
  handleOnClick: handleDelete,
407
- label: t("actionRowDelete"),
430
+ label: t("actionsButtonDelete"),
408
431
  order: 3,
409
432
  variant: "destructive",
410
433
  },
411
434
  {
412
435
  enabled: false,
413
436
  handleOnClick: toggleStatus,
414
- label: t("actionRowToggleStatus"),
437
+ label: t("actionsButtonToggleStatus"),
415
438
  order: 4,
416
439
  },
417
440
  ], [handleDelete, handleEdit, handleView, t, toggleStatus]);
418
441
  // ============================================================================
419
442
  // 1.4.9 EFFECTS
420
443
  // ============================================================================
421
- // Keep the latest fetch function in a ref so the fetch effect can call it
422
- // without depending on listFetchNow's unstable identity.
423
444
  useEffect(() => {
424
445
  listFetchNowRef.current = listFetchNow;
425
- });
426
- // Initial load via cache; re-fetch directly from API on page/pageLimit/filter/search changes.
446
+ }, [listFetchNow]);
427
447
  useEffect(() => {
428
448
  var _a;
429
- if (!schoolId)
430
- return;
431
- const currentPage = Number(listParams.currentPage) || 1;
432
- const currentPageLimit = Number(listParams.pageLimit) || pageLimit;
433
- const isDefaultLoad = currentPage === 1 &&
434
- currentPageLimit === pageLimit &&
435
- !listParams.searchQuery &&
436
- listParams.filterEnabled === undefined;
437
- if (isDefaultLoad) {
438
- (async () => {
439
- try {
440
- const { count, items } = await getCachedSections({
441
- params: listParams,
442
- });
443
- dispatch({
444
- type: SECTION_ACTION_TYPES.SET_ITEMS,
445
- payload: { items: items || [], count: count || 0 },
446
- });
447
- }
448
- catch (_a) {
449
- showToast(t("messagesFetchFailed"), TOAST_VARIANT.ERROR);
450
- }
451
- })();
452
- }
453
- else {
454
- // Bypass cache for pagination, pageLimit, filter and search changes.
455
- // Use ref to avoid the infinite-loop caused by listFetchNow's unstable identity.
456
- (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
457
- }
458
- }, [dispatch, listParams, schoolId, showToast, t]);
449
+ (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
450
+ }, [listParams]);
459
451
  // ============================================================================
460
452
  // 1.4.10 RETURN
461
453
  // ============================================================================
@@ -480,6 +472,7 @@ export const useSectionModule = () => {
480
472
  headerActions,
481
473
  listFetchNow,
482
474
  listLoading,
475
+ resetRecordFormState,
483
476
  rowActions,
484
477
  toggleStatus,
485
478
  updateLoading });
@@ -7,95 +7,40 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * Uses a class combobox populated from cached class data.
8
8
  */
9
9
  import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
10
- import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
10
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
11
11
  import { EnhancedCheckbox } from "@appcorp/shadcn/components/enhanced-checkbox";
12
- import { useDebounce } from "@react-pakistan/util-functions/hooks/use-debounce";
13
- import { useEffect, useMemo, useState } from "react";
14
12
  import { useSectionModule } from "./context";
15
13
  import { useTranslations } from "next-intl";
16
- import { getCachedClasses, getCachedClassesSync } from "../class/cache";
17
14
  import { getCachedWorkspaceSync } from "../workspace/cache";
15
+ import { SECTION_API_ROUTES } from "./constants";
16
+ import { useFetch } from "@react-pakistan/util-functions/hooks/use-fetch";
17
+ import { API_METHODS } from "@react-pakistan/util-functions";
18
18
  export const SectionForm = () => {
19
- var _a;
20
19
  const { state, handleChange } = useSectionModule();
21
20
  const { capacity, classId, enabled, errors, name } = state;
22
21
  const t = useTranslations("section");
23
22
  const workspace = getCachedWorkspaceSync();
24
- const schoolId = ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "";
25
- const cachedClasses = getCachedClassesSync();
26
- const [classSearchQuery, setClassSearchQuery] = useState("");
27
- const [remoteClassOptions, setRemoteClassOptions] = useState(() => cachedClasses.items.map((cls) => ({
28
- label: `${cls.name} (${cls.code})`,
29
- value: cls.id,
30
- })));
31
- const [classOptionsLoading, setClassOptionsLoading] = useState(false);
32
- const debouncedClassSearchQuery = useDebounce(classSearchQuery, 300);
33
- const trimmedClassSearchQuery = debouncedClassSearchQuery.trim();
34
- const cachedClassOptions = useMemo(() => cachedClasses.items.map((cls) => ({
35
- label: `${cls.name} (${cls.code})`,
36
- value: cls.id,
37
- })), [cachedClasses.items]);
38
- const selectedClassOption = useMemo(() => {
39
- const selectedClass = cachedClasses.items.find((item) => item.id === classId);
40
- return selectedClass
41
- ? [
42
- {
43
- label: `${selectedClass.name} (${selectedClass.code})`,
44
- value: selectedClass.id,
45
- },
46
- ]
47
- : [];
48
- }, [cachedClasses.items, classId]);
49
- const displayedClassOptions = useMemo(() => {
50
- const sourceOptions = trimmedClassSearchQuery
51
- ? remoteClassOptions
52
- : cachedClassOptions;
53
- const mergedOptions = [...selectedClassOption, ...sourceOptions];
54
- const uniqueOptions = new Map(mergedOptions.map((option) => [option.value, option]));
55
- return [...uniqueOptions.values()];
56
- }, [
57
- cachedClassOptions,
58
- remoteClassOptions,
59
- selectedClassOption,
60
- trimmedClassSearchQuery,
61
- ]);
62
- useEffect(() => {
63
- if (!trimmedClassSearchQuery || !schoolId)
64
- return;
65
- let isActive = true;
66
- const fetchClasses = async () => {
67
- setClassOptionsLoading(true);
68
- try {
69
- const { items } = await getCachedClasses({
70
- params: {
71
- schoolId,
72
- searchQuery: trimmedClassSearchQuery,
73
- },
74
- });
75
- if (!isActive)
76
- return;
77
- setRemoteClassOptions((items || []).map((cls) => ({
78
- label: `${cls.name} (${cls.code})`,
79
- value: cls.id,
80
- })));
81
- }
82
- catch (_a) {
83
- if (!isActive)
84
- return;
85
- setRemoteClassOptions([]);
86
- }
87
- finally {
88
- if (isActive) {
89
- setClassOptionsLoading(false);
90
- }
91
- }
92
- };
93
- void fetchClasses();
94
- return () => {
95
- isActive = false;
96
- };
97
- }, [schoolId, trimmedClassSearchQuery]);
98
- return (_jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedCombobox, { emptyText: t("formNoClassEmpty"), error: errors.classId, id: "classId", info: t("formClassInfo"), label: t("formClassLabel"), loading: classOptionsLoading && Boolean(trimmedClassSearchQuery), onSearchChange: setClassSearchQuery, onValueChange: (value) => handleChange("classId", value), options: displayedClassOptions, required: true, searchPlaceholder: t("formSearchClassesPlaceholder"), value: classId }), _jsx(EnhancedInput, { error: errors.name, id: "name", info: t("sectionNameInfo"), label: t("sectionName"), onChange: (e) => handleChange("name", e.target.value), placeholder: t("sectionNamePlaceholder"), required: true, value: name }), _jsx(EnhancedInput, { error: errors.capacity, id: "capacity", info: t("formCapacityInfo"), label: t("formCapacityLabel"), onChange: (e) => {
23
+ const { data: classes } = useFetch("/api/v1/class", {
24
+ params: { workspaceId: workspace === null || workspace === void 0 ? void 0 : workspace.id },
25
+ method: API_METHODS.GET,
26
+ });
27
+ const { enhancedComboboxElement: classIdCombo } = useEnhancedCombobox({
28
+ emptyText: t("formNoClassEmpty"),
29
+ id: "classId",
30
+ info: t("formClassInfo"),
31
+ label: t("formClassLabel"),
32
+ onValueChange: (value) => handleChange("classId", value),
33
+ options: (classes === null || classes === void 0 ? void 0 : classes.map((cls) => ({
34
+ id: cls.id,
35
+ name: `${cls.name} (${cls.code})`,
36
+ }))) || [],
37
+ placeholder: t("formClassPlaceholder"),
38
+ required: true,
39
+ searchEndpoint: SECTION_API_ROUTES.UNIT,
40
+ searchPlaceholder: t("formSearchClassesPlaceholder"),
41
+ value: classId,
42
+ });
43
+ return (_jsxs("div", { className: "grid grid-cols-1 gap-4", children: [classIdCombo, _jsx(EnhancedInput, { error: errors.name, id: "name", info: t("sectionNameInfo"), label: t("sectionName"), onChange: (e) => handleChange("name", e.target.value), placeholder: t("sectionNamePlaceholder"), required: true, value: name }), _jsx(EnhancedInput, { error: errors.capacity, id: "capacity", info: t("formCapacityInfo"), label: t("formCapacityLabel"), onChange: (e) => {
99
44
  const value = e.target.value;
100
45
  handleChange("capacity", value ? Number(value) : 0);
101
46
  }, placeholder: t("formCapacityPlaceholder"), type: "number", value: (capacity === null || capacity === void 0 ? void 0 : capacity.toString()) || "" }), _jsx(EnhancedCheckbox, { label: t("formActiveSectionLabel"), defaultChecked: enabled, onCheckedChange: (checked) => handleChange("enabled", checked), info: t("actionToggleActivateOrDeactivateSection") })] }));
@@ -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 { SECTION_API_ROUTES, pageLimit } from "./constants";
10
- import { invalidateSectionsCache } from "./cache";
11
10
  import { SECTION_ACTION_TYPES, useSectionContext } from "./context";
12
11
  import { useRef, useEffect, useCallback } from "react";
13
12
  const workspace = getCachedWorkspaceSync();
@@ -165,7 +164,6 @@ export const SectionMoreActions = () => {
165
164
  else {
166
165
  showSuccessToast("Bulk operation completed successfully");
167
166
  }
168
- invalidateSectionsCache();
169
167
  const schoolId = ((_f = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _f === void 0 ? void 0 : _f.id) || "";
170
168
  fetch(`${SECTION_API_ROUTES.LIST}?currentPage=1&pageLimit=${pageLimit}&schoolId=${schoolId}`, {
171
169
  headers: {
@@ -12,17 +12,19 @@ import { GraduationCap, CheckCircle2, XCircle, Users } from "lucide-react";
12
12
  import { useSectionModule } from "./context";
13
13
  import { useTranslations } from "next-intl";
14
14
  import { formatValue } from "@react-pakistan/util-functions/general/format-value";
15
- import { getCachedClassesSync } from "../class/cache";
15
+ import { getCachedWorkspaceSync } from "../workspace/cache";
16
+ import { useFetch } from "@react-pakistan/util-functions/hooks/use-fetch";
17
+ import { API_METHODS } from "@react-pakistan/util-functions";
16
18
  export const SectionView = () => {
17
19
  const { state } = useSectionModule();
18
20
  const { capacity, classId, enabled, name } = state;
21
+ const workspace = getCachedWorkspaceSync();
19
22
  const t = useTranslations("section");
20
- const classes = getCachedClassesSync();
21
- const selectedClass = classes.items.find((cls) => cls.id === classId);
22
- const className = selectedClass
23
- ? `${selectedClass.name} (${selectedClass.code})`
24
- : "—";
25
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-center gap-6", children: [_jsx("div", { className: "bg-primary/10 flex h-24 w-24 items-center justify-center rounded-full", children: _jsx(Users, { className: "text-primary h-12 w-12" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h2", { className: "text-2xl font-bold", children: name }), enabled ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" }))] }), _jsxs("p", { className: "text-muted-foreground mt-1", children: [t("viewFieldClass"), ": ", className] })] })] }) }) }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(GraduationCap, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("sectionDetails") })] }), _jsx(CardDescription, { children: t("viewSectionCompleteSectionInformation") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("sectionName") }), _jsx("p", { className: "text-base", children: formatValue(name) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldClass") }), _jsx("p", { className: "text-base", children: className })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldCapacity") }), _jsxs("p", { className: "text-base", children: [formatValue(capacity), " ", capacity !== null ? t("viewFieldStudents") : ""] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldStatus") }), _jsx(Badge, { variant: enabled ? "default" : "secondary", children: enabled
23
+ const { data: classData } = useFetch("/api/v1/class", {
24
+ params: { classId, workspaceId: workspace === null || workspace === void 0 ? void 0 : workspace.id },
25
+ method: API_METHODS.GET,
26
+ });
27
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-center gap-6", children: [_jsx("div", { className: "bg-primary/10 flex h-24 w-24 items-center justify-center rounded-full", children: _jsx(Users, { className: "text-primary h-12 w-12" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h2", { className: "text-2xl font-bold", children: name }), enabled ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" }))] }), _jsxs("p", { className: "text-muted-foreground mt-1", children: [t("viewFieldClass"), ": ", (classData === null || classData === void 0 ? void 0 : classData.name) || "—"] })] })] }) }) }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(GraduationCap, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("sectionDetails") })] }), _jsx(CardDescription, { children: t("viewSectionCompleteSectionInformation") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("sectionName") }), _jsx("p", { className: "text-base", children: formatValue(name) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldClass") }), _jsx("p", { className: "text-base", children: (classData === null || classData === void 0 ? void 0 : classData.name) || "—" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldCapacity") }), _jsxs("p", { className: "text-base", children: [formatValue(capacity), " ", capacity !== null ? t("viewFieldStudents") : ""] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldStatus") }), _jsx(Badge, { variant: enabled ? "default" : "secondary", children: enabled
26
28
  ? t("viewFieldStatusActive")
27
29
  : t("viewFieldStatusInactive") })] })] }) })] })] }));
28
30
  };
@@ -26,6 +26,7 @@ export declare const useStudentFeeModule: () => {
26
26
  }[];
27
27
  listFetchNow: (url?: string, config?: import("@react-pakistan/util-functions/hooks/use-fetch").FetchConfig) => void;
28
28
  listLoading: boolean;
29
+ resetRecordFormState: () => void;
29
30
  rowActions: RowAction[];
30
31
  toggleStatus: (row?: TableRow) => void;
31
32
  updateLoading: boolean;
@@ -7,9 +7,8 @@ import { useModuleEntityV2, } from "@react-pakistan/util-functions/hooks/use-mod
7
7
  import { useDebounce } from "@react-pakistan/util-functions/hooks/use-debounce";
8
8
  import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
9
9
  import { PAYMENT_STATUS, DISCOUNT_TYPE, } from "../../../type";
10
- import { STUDENT_FEE_API_ROUTES, pageLimit } from "../constants";
10
+ import { STUDENT_FEE_API_ROUTES } from "../constants";
11
11
  import { studentFeeFormValidation } from "../validate";
12
- import { getCachedStudentFees, invalidateStudentFeesCache } from "../cache";
13
12
  import { getCachedWorkspaceSync } from "../../workspace/cache";
14
13
  import { getCachedFeeStructureById } from "../../fee-structure/cache";
15
14
  import { getCachedStudentProfilesSync } from "../../student-profile/cache";
@@ -71,11 +70,6 @@ export const useStudentFeeModule = () => {
71
70
  }), [state, schoolId]);
72
71
  const byIdParams = useMemo(() => ({ id: state.id }), [state.id]);
73
72
  const deleteParams = useMemo(() => ({ id: state.id }), [state.id]);
74
- const isDefaultListState = state.currentPage === 1 &&
75
- state.pageLimit === pageLimit &&
76
- !debouncedQuery &&
77
- state.filterEnabled === undefined &&
78
- !state.filterStatus;
79
73
  // ============================================================================
80
74
  // 1.4.3 UTILITIES
81
75
  // ============================================================================
@@ -101,6 +95,44 @@ export const useStudentFeeModule = () => {
101
95
  payload: { drawer: null },
102
96
  });
103
97
  }, [dispatch]);
98
+ const resetRecordFormState = useCallback(() => {
99
+ dispatch({
100
+ type: STUDENT_FEE_ACTION_TYPES.SET_ERRORS,
101
+ payload: { errors: {} },
102
+ });
103
+ dispatch({
104
+ type: STUDENT_FEE_ACTION_TYPES.SET_DISABLE_SAVE_BUTTON,
105
+ payload: { disabled: false },
106
+ });
107
+ dispatch({
108
+ type: STUDENT_FEE_ACTION_TYPES.SET_FORM_DATA,
109
+ payload: {
110
+ form: {
111
+ amount: 0,
112
+ amountDue: 0,
113
+ amountPaid: 0,
114
+ discountAmount: 0,
115
+ discountCodeId: null,
116
+ dueDate: new Date().toISOString().slice(0, 10),
117
+ enabled: true,
118
+ familyId: "",
119
+ feeStructureId: "",
120
+ filterEnabled: undefined,
121
+ filterStatus: "",
122
+ id: "",
123
+ lastRemindedAt: null,
124
+ nextFollowUpAt: null,
125
+ originalFee: 0,
126
+ reliabilityScore: null,
127
+ remarks: null,
128
+ riskLevel: null,
129
+ schoolId,
130
+ status: PAYMENT_STATUS.PENDING,
131
+ studentProfileId: "",
132
+ },
133
+ },
134
+ });
135
+ }, [dispatch, schoolId]);
104
136
  // ============================================================================
105
137
  // 1.4.4 API CALLBACKS
106
138
  // ============================================================================
@@ -126,7 +158,6 @@ export const useStudentFeeModule = () => {
126
158
  }
127
159
  if (data) {
128
160
  const isCreated = isCreatedOrUpdated(data);
129
- invalidateStudentFeesCache();
130
161
  showToast(isCreated
131
162
  ? t("messagesStudentFeeCreated")
132
163
  : t("messagesStudentFeeUpdated"), TOAST_VARIANT.SUCCESS);
@@ -162,7 +193,6 @@ export const useStudentFeeModule = () => {
162
193
  return;
163
194
  }
164
195
  if (data) {
165
- invalidateStudentFeesCache();
166
196
  showToast(t("messagesStudentFeeDeleted"), TOAST_VARIANT.SUCCESS);
167
197
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
168
198
  }
@@ -424,19 +454,21 @@ export const useStudentFeeModule = () => {
424
454
  });
425
455
  }, [dispatch]);
426
456
  const handleView = useCallback((row) => {
457
+ resetRecordFormState();
427
458
  byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
428
459
  dispatch({
429
460
  type: STUDENT_FEE_ACTION_TYPES.SET_DRAWER,
430
461
  payload: { drawer: STUDENT_FEE_DRAWER.VIEW_DRAWER },
431
462
  });
432
- }, [dispatch, byIdFetchNow]);
463
+ }, [byIdFetchNow, dispatch, resetRecordFormState]);
433
464
  const handleEdit = useCallback((row) => {
465
+ resetRecordFormState();
434
466
  byIdFetchNow === null || byIdFetchNow === void 0 ? void 0 : byIdFetchNow(undefined, { params: { id: row === null || row === void 0 ? void 0 : row.id } });
435
467
  dispatch({
436
468
  type: STUDENT_FEE_ACTION_TYPES.SET_DRAWER,
437
469
  payload: { drawer: STUDENT_FEE_DRAWER.FORM_DRAWER },
438
470
  });
439
- }, [dispatch, byIdFetchNow]);
471
+ }, [byIdFetchNow, dispatch, resetRecordFormState]);
440
472
  const handleDelete = useCallback((row) => {
441
473
  if (!confirm(t("messagesDeleteConfirmation")))
442
474
  return;
@@ -555,32 +587,15 @@ export const useStudentFeeModule = () => {
555
587
  // ============================================================================
556
588
  // 1.4.9 EFFECTS
557
589
  // ============================================================================
590
+ useEffect(() => {
591
+ listFetchNowRef.current = listFetchNow;
592
+ }, [listFetchNow]);
558
593
  useEffect(() => {
559
594
  var _a;
560
595
  if (!schoolId)
561
596
  return;
562
- if (isDefaultListState) {
563
- (async () => {
564
- try {
565
- const { count, items } = await getCachedStudentFees({
566
- params: listParams,
567
- });
568
- dispatch({
569
- type: STUDENT_FEE_ACTION_TYPES.SET_ITEMS,
570
- payload: { items: items || [], count: count || 0 },
571
- });
572
- }
573
- catch (_a) {
574
- showToast(t("messagesNetworkError"), TOAST_VARIANT.ERROR);
575
- }
576
- })();
577
- return;
578
- }
579
597
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
580
- }, [dispatch, isDefaultListState, listParams, schoolId, showToast, t]);
581
- useEffect(() => {
582
- listFetchNowRef.current = listFetchNow;
583
- }, [listFetchNow]);
598
+ }, [dispatch, listParams, schoolId, showToast, t]);
584
599
  // ============================================================================
585
600
  // 1.4.10 RETURN
586
601
  // ============================================================================
@@ -604,6 +619,7 @@ export const useStudentFeeModule = () => {
604
619
  headerActions,
605
620
  listFetchNow,
606
621
  listLoading,
622
+ resetRecordFormState,
607
623
  rowActions,
608
624
  toggleStatus,
609
625
  updateLoading });
@@ -8,7 +8,7 @@
8
8
  * Key responsibilities:
9
9
  * - expose `useStudentFeeModule()` which UI components call for actions
10
10
  * - keep module-specific `apiParams` and callbacks in one place
11
- * - ensure cache invalidation and toast notifications on mutation
11
+ * - ensure toast notifications on mutation
12
12
  *
13
13
  * Exported utilities:
14
14
  * - `StudentFeeProvider` — provider component used by the page
@@ -9,7 +9,7 @@
9
9
  * Key responsibilities:
10
10
  * - expose `useStudentFeeModule()` 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
  * - `StudentFeeProvider` — provider component used by the page
@@ -7,20 +7,40 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * filterStatus (payment status combobox).
8
8
  */
9
9
  import { EnhancedRadio } from "@appcorp/shadcn/components/enhanced-radio";
10
- import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
10
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
11
11
  import { useStudentFeeModule } from "./context";
12
- import { PAYMENT_STATUS_OPTIONS } from "./constants";
12
+ import { PAYMENT_STATUS_OPTIONS, STUDENT_FEE_API_ROUTES } from "./constants";
13
13
  import { useTranslations } from "next-intl";
14
14
  export const StudentFeeFilter = () => {
15
15
  const { state, handleChange } = useStudentFeeModule();
16
16
  const { filterEnabled, filterStatus } = state;
17
17
  const t = useTranslations("studentFee");
18
+ const paymentStatusLabelMap = {
19
+ PENDING: t("formOptionPending"),
20
+ PARTIAL: t("formOptionPartial"),
21
+ PAID: t("formOptionPaid"),
22
+ OVERDUE: t("formOptionOverdue"),
23
+ CANCELLED: t("formOptionCancelled"),
24
+ REFUNDED: t("formOptionRefunded"),
25
+ };
18
26
  const filterEnabledValue = filterEnabled === undefined
19
27
  ? "undefined"
20
28
  : filterEnabled
21
29
  ? "true"
22
30
  : "false";
23
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedCombobox, { emptyText: t("filterNoStatusFoundLabel"), id: "filterStatus", info: t("filterByPaymentStatus"), label: t("filterPaymentStatusLabel"), onValueChange: (value) => handleChange("filterStatus", value), options: PAYMENT_STATUS_OPTIONS, placeholder: t("filterAllStatusesLabel"), searchPlaceholder: t("filterSearchStatusLabel"), value: filterStatus || "" }), _jsx(EnhancedRadio, { label: t("filterOptionEnabled"), name: "filterEnabled", value: filterEnabledValue, options: [
31
+ const { enhancedComboboxElement: filterStatusCombo } = useEnhancedCombobox({
32
+ emptyText: t("filterNoStatusFoundLabel"),
33
+ id: "filterStatus",
34
+ info: t("filterByPaymentStatus"),
35
+ label: t("filterPaymentStatusLabel"),
36
+ onValueChange: (value) => handleChange("filterStatus", value),
37
+ options: PAYMENT_STATUS_OPTIONS.map((opt) => ({ id: opt.value, name: paymentStatusLabelMap[opt.value] || opt.label })),
38
+ placeholder: t("filterAllStatusesLabel"),
39
+ searchEndpoint: STUDENT_FEE_API_ROUTES.UNIT,
40
+ searchPlaceholder: t("filterSearchStatusLabel"),
41
+ value: filterStatus || "",
42
+ });
43
+ return (_jsxs("div", { className: "space-y-4", children: [filterStatusCombo, _jsx(EnhancedRadio, { label: t("filterOptionEnabled"), name: "filterEnabled", value: filterEnabledValue, options: [
24
44
  { label: t("filterOptionAll"), value: "undefined" },
25
45
  { label: t("filterOptionEnabled"), value: "true" },
26
46
  { label: t("filterOptionDisabled"), value: "false" },