@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.
- package/base-modules/admission/constants.d.ts +5 -17
- package/base-modules/admission/constants.js +12 -7
- package/base-modules/admission/context/use-admission-module.js +11 -48
- package/base-modules/admission/filter.js +23 -3
- package/base-modules/admission/form.js +49 -19
- package/base-modules/attendance/context.js +3 -37
- package/base-modules/attendance/filter.js +3 -1
- package/base-modules/attendance/form.js +26 -10
- package/base-modules/attendance/more-actions.js +34 -25
- package/base-modules/campus/context.js +7 -7
- package/base-modules/class/cache.js +0 -1
- package/base-modules/class/context.js +10 -48
- package/base-modules/class/more-actions.js +47 -24
- package/base-modules/course/context.js +3 -37
- package/base-modules/course/form.js +91 -292
- package/base-modules/discount-code/constants.d.ts +5 -0
- package/base-modules/discount-code/constants.js +5 -0
- package/base-modules/discount-code/context.d.ts +1 -0
- package/base-modules/discount-code/context.js +40 -39
- package/base-modules/discount-code/form.js +21 -15
- package/base-modules/discount-code/more-actions.js +1 -1
- package/base-modules/enrollment/context.js +3 -37
- package/base-modules/enrollment/form.js +38 -11
- package/base-modules/enrollment/more-actions.js +48 -25
- package/base-modules/expense/constants.js +1 -1
- package/base-modules/expense/context.js +5 -32
- package/base-modules/expense/filter.js +50 -3
- package/base-modules/expense/form.js +82 -6
- package/base-modules/family/context.js +7 -38
- package/base-modules/family-member/context.js +7 -39
- package/base-modules/fee-structure/context.js +1 -25
- package/base-modules/fee-structure/form.js +77 -89
- package/base-modules/fee-structure/more-actions.js +45 -22
- package/base-modules/rbac/context.d.ts +1 -0
- package/base-modules/rbac/context.js +24 -33
- package/base-modules/rbac/form.js +3 -1
- package/base-modules/school/context.js +1 -1
- package/base-modules/school/form.js +34 -14
- package/base-modules/section/context.d.ts +1 -0
- package/base-modules/section/context.js +40 -47
- package/base-modules/section/form.js +25 -80
- package/base-modules/section/more-actions.js +47 -24
- package/base-modules/section/view.js +9 -7
- package/base-modules/student-fee/context/use-student-fee-module.d.ts +1 -0
- package/base-modules/student-fee/context/use-student-fee-module.js +48 -32
- package/base-modules/student-fee/context.d.ts +1 -1
- package/base-modules/student-fee/context.js +1 -1
- package/base-modules/student-fee/filter.js +23 -3
- package/base-modules/student-fee/form.js +93 -174
- package/base-modules/student-fee/view.d.ts +7 -1
- package/base-modules/student-fee/view.js +17 -20
- package/base-modules/student-profile/constants.d.ts +0 -6
- package/base-modules/student-profile/constants.js +1 -3
- package/base-modules/student-profile/context/use-student-profile-module.d.ts +1 -0
- package/base-modules/student-profile/context/use-student-profile-module.js +62 -55
- package/base-modules/student-profile/context.d.ts +1 -1
- package/base-modules/student-profile/context.js +1 -1
- package/base-modules/student-profile/filter.js +24 -4
- package/base-modules/student-profile/form.js +35 -3
- package/base-modules/subject/context.d.ts +1 -0
- package/base-modules/subject/context.js +38 -47
- package/base-modules/subject/more-actions.js +47 -24
- package/base-modules/teacher/constants.d.ts +0 -6
- package/base-modules/teacher/constants.js +0 -2
- package/base-modules/teacher/context.d.ts +1 -0
- package/base-modules/teacher/context.js +58 -39
- package/base-modules/teacher/form.js +46 -11
- package/base-modules/teacher/more-actions.js +45 -22
- package/base-modules/user/context/use-user-module.d.ts +1 -0
- package/base-modules/user/context/use-user-module.js +36 -32
- package/base-modules/user/context.js +1 -1
- package/base-modules/user/filter.js +6 -4
- package/base-modules/user/form.js +29 -5
- package/base-modules/user/more-actions.js +9 -7
- package/base-modules/user/view.js +3 -1
- package/base-modules/workspace/form.js +18 -8
- package/base-modules/workspace-user/context.d.ts +2 -1
- package/base-modules/workspace-user/context.js +31 -29
- package/package.json +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/base-modules/admission/cache.d.ts +0 -14
- package/base-modules/admission/cache.js +0 -31
- package/base-modules/attendance/cache.d.ts +0 -14
- package/base-modules/attendance/cache.js +0 -31
- package/base-modules/course/cache.d.ts +0 -14
- package/base-modules/course/cache.js +0 -31
- package/base-modules/enrollment/cache.d.ts +0 -14
- package/base-modules/enrollment/cache.js +0 -31
- package/base-modules/expense/cache.d.ts +0 -14
- package/base-modules/expense/cache.js +0 -31
- package/base-modules/family/cache.d.ts +0 -14
- package/base-modules/family/cache.js +0 -31
- package/base-modules/family-member/cache.d.ts +0 -14
- package/base-modules/family-member/cache.js +0 -31
- package/base-modules/rbac/cache.d.ts +0 -27
- package/base-modules/rbac/cache.js +0 -46
- package/base-modules/student-fee/cache.d.ts +0 -15
- package/base-modules/student-fee/cache.js +0 -21
- package/base-modules/workspace-user/cache.d.ts +0 -14
- 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 {
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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(
|
|
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("
|
|
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("
|
|
121
|
+
msgs.push(t("validationRequiredName"));
|
|
121
122
|
if (!((_c = row.classId) === null || _c === void 0 ? void 0 : _c.trim()))
|
|
122
|
-
msgs.push("
|
|
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("
|
|
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) =>
|
|
136
|
+
.map((e) => t("messagesBulkRowError", {
|
|
137
|
+
row: e.row,
|
|
138
|
+
error: e.messages.join("; "),
|
|
139
|
+
}))
|
|
136
140
|
.join("\n");
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
showErrorToast(t("messagesBulkValidationFailed", {
|
|
142
|
+
count: validationErrors.length,
|
|
143
|
+
errors: summary,
|
|
144
|
+
}));
|
|
139
145
|
return;
|
|
140
146
|
}
|
|
141
147
|
try {
|
|
142
|
-
showInfoToast(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
180
|
+
showSuccessToast(t("messagesBulkResults", {
|
|
181
|
+
created: r.created,
|
|
182
|
+
updated: r.updated,
|
|
183
|
+
skipped: r.skipped,
|
|
184
|
+
}));
|
|
164
185
|
}
|
|
165
186
|
else {
|
|
166
|
-
showSuccessToast("
|
|
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
|
-
: "
|
|
196
|
-
showErrorToast(
|
|
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(
|
|
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 {
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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" },
|