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