@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,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();
@@ -68,13 +67,15 @@ async function pollBulkJob(jobId, signal, onProgress) {
68
67
  }
69
68
  }
70
69
  }
71
- function formatErrorSummary(errors) {
70
+ function formatErrorSummary(t, errors) {
72
71
  if (!(errors === null || errors === void 0 ? void 0 : errors.length))
73
72
  return "";
74
- const lines = errors.slice(0, 5).map((e) => `Row ${e.row}: ${e.error}`);
73
+ const lines = errors
74
+ .slice(0, 5)
75
+ .map((e) => t("messagesBulkRowError", { row: e.row, error: e.error }));
75
76
  const remaining = errors.length - 5;
76
77
  if (remaining > 0)
77
- lines.push(`...and ${remaining} more`);
78
+ lines.push(t("messagesBulkMoreRows", { count: remaining }));
78
79
  return lines.join("\n");
79
80
  }
80
81
  export const SectionMoreActions = () => {
@@ -107,7 +108,7 @@ export const SectionMoreActions = () => {
107
108
  const text = await file.text();
108
109
  const records = converter.csv2json(text);
109
110
  if (!Array.isArray(records) || records.length === 0) {
110
- showErrorToast("CSV file is empty or invalid");
111
+ showErrorToast(t("messagesBulkCsvEmpty"));
111
112
  return;
112
113
  }
113
114
  // Client-side validation — basic required field check
@@ -117,13 +118,13 @@ export const SectionMoreActions = () => {
117
118
  const msgs = [];
118
119
  if (method === "POST") {
119
120
  if (!((_b = row.name) === null || _b === void 0 ? void 0 : _b.trim()))
120
- msgs.push("name is required");
121
+ msgs.push(t("validationRequiredName"));
121
122
  if (!((_c = row.classId) === null || _c === void 0 ? void 0 : _c.trim()))
122
- msgs.push("classId is required");
123
+ msgs.push(t("validationRequiredClassId"));
123
124
  }
124
125
  else {
125
126
  if (!((_d = row.id) === null || _d === void 0 ? void 0 : _d.trim()))
126
- msgs.push("id is required for update");
127
+ msgs.push(t("validationRequiredIdForUpdate"));
127
128
  }
128
129
  if (msgs.length > 0) {
129
130
  validationErrors.push({ row: i + 1, messages: msgs });
@@ -132,40 +133,59 @@ export const SectionMoreActions = () => {
132
133
  if (validationErrors.length > 0) {
133
134
  const summary = validationErrors
134
135
  .slice(0, 5)
135
- .map((e) => `Row ${e.row}: ${e.messages.join("; ")}`)
136
+ .map((e) => t("messagesBulkRowError", {
137
+ row: e.row,
138
+ error: e.messages.join("; "),
139
+ }))
136
140
  .join("\n");
137
- const remaining = validationErrors.length - 5;
138
- showErrorToast(`Validation failed for ${validationErrors.length} row(s).\n${summary}${remaining > 0 ? `\n...and ${remaining} more` : ""}`);
141
+ showErrorToast(t("messagesBulkValidationFailed", {
142
+ count: validationErrors.length,
143
+ errors: summary,
144
+ }));
139
145
  return;
140
146
  }
141
147
  try {
142
- showInfoToast(`Bulk ${label} job queued (${records.length} records). Processing...`);
148
+ showInfoToast(t("messagesBulkJobQueued", { action: label, count: records.length }));
143
149
  let jobId;
144
150
  try {
145
151
  jobId = await submitBulkJob(text, method, signal);
146
152
  }
147
153
  catch (submitError) {
148
- showErrorToast(`Failed to submit ${label} job: ${submitError instanceof Error ? submitError.message : "Unknown error"}`);
154
+ showErrorToast(t("messagesBulkJobSubmitFailed", {
155
+ action: label,
156
+ error: submitError instanceof Error
157
+ ? submitError.message
158
+ : t("unknownError"),
159
+ }));
149
160
  return;
150
161
  }
151
162
  const status = await pollBulkJob(jobId, signal, (processed, total) => {
152
- showInfoToast(`Processing ${processed}/${total} sections...`);
163
+ showInfoToast(t("messagesBulkProgress", { processed, total }));
153
164
  });
154
165
  if (signal.aborted)
155
166
  return;
156
167
  if (status.status === "completed") {
157
168
  const r = status.results;
158
169
  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}`);
170
+ const summary = formatErrorSummary(t, r.errors);
171
+ showSuccessToast(t("messagesBulkResults", {
172
+ created: r.created,
173
+ updated: r.updated,
174
+ skipped: r.skipped,
175
+ }) +
176
+ "\n" +
177
+ 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
- invalidateSectionsCache();
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(`${SECTION_API_ROUTES.LIST}?currentPage=1&pageLimit=${pageLimit}&schoolId=${schoolId}`, {
171
191
  headers: {
@@ -191,17 +211,20 @@ export const SectionMoreActions = () => {
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 = [
@@ -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" },