@appcorp/fusion-storybook 0.4.10 → 0.4.12
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.
|
@@ -34,7 +34,7 @@ import { getCachedWorkspaceSync } from "../workspace/cache";
|
|
|
34
34
|
export const formatStudentName = (_, row) => {
|
|
35
35
|
var _a, _b, _c;
|
|
36
36
|
const fee = row;
|
|
37
|
-
const member = (_b = (_a = fee === null || fee === void 0 ? void 0 : fee.family) === null || _a === void 0 ? void 0 : _a.members) === null || _b === void 0 ? void 0 : _b.find(({
|
|
37
|
+
const member = (_b = (_a = fee === null || fee === void 0 ? void 0 : fee.family) === null || _a === void 0 ? void 0 : _a.members) === null || _b === void 0 ? void 0 : _b.find(({ studentProfile }) => fee.studentProfileId === (studentProfile === null || studentProfile === void 0 ? void 0 : studentProfile.id));
|
|
38
38
|
return member
|
|
39
39
|
? `${member.firstName} ${member.lastName} - ${(_c = member.studentProfile) === null || _c === void 0 ? void 0 : _c.computerNumber.split(":")[1]}`
|
|
40
40
|
: "N/A";
|
|
@@ -1,62 +1,32 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { downloadFromUrl } from "@react-pakistan/util-functions/general/download-from-url";
|
|
4
3
|
import { showErrorToast, showSuccessToast, showInfoToast, } from "@appcorp/shadcn/lib/toast-utils";
|
|
5
4
|
import { getCachedWorkspaceSync } from "../workspace/cache";
|
|
6
|
-
import converter from "json-2-csv";
|
|
7
|
-
import { Timeline } from "../../components/timeline";
|
|
8
5
|
import { useTranslations } from "next-intl";
|
|
9
6
|
import { STUDENT_FEE_API_ROUTES } from "../../constants";
|
|
10
7
|
import { STUDENT_FEE_ACTION_TYPES, useStudentFeeModule } from "./context";
|
|
11
|
-
import { useRef, useEffect, useCallback } from "react";
|
|
8
|
+
import { useRef, useEffect, useCallback, useState } from "react";
|
|
12
9
|
import { Button } from "@appcorp/shadcn/components/ui/button";
|
|
13
10
|
import { Separator } from "@appcorp/shadcn/components/ui/separator";
|
|
11
|
+
import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
|
|
12
|
+
import { EnhancedTextarea } from "@appcorp/shadcn/components/enhanced-textarea";
|
|
14
13
|
const workspace = getCachedWorkspaceSync();
|
|
15
14
|
const POLL_INTERVAL_MS = 2000;
|
|
16
15
|
const POLL_TIMEOUT_MS = 300000;
|
|
17
16
|
const PAGE_LIMIT = 5000;
|
|
18
|
-
|
|
19
|
-
const response = await fetch(`${STUDENT_FEE_API_ROUTES.UNIT}?pageLimit=${pageLimit}¤tPage=1&schoolId=${schoolId}&studentComputerNumber=true&status=PENDING`);
|
|
20
|
-
const result = await response.json();
|
|
21
|
-
const csv = await converter.json2csv(result.items || [], {});
|
|
22
|
-
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
23
|
-
await downloadFromUrl(blob, "student-fees.csv");
|
|
24
|
-
};
|
|
25
|
-
async function submitBulkJob(csvData, method, signal) {
|
|
26
|
-
var _a;
|
|
27
|
-
const schoolId = ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "";
|
|
28
|
-
if (!schoolId)
|
|
29
|
-
throw new Error("School ID not found");
|
|
30
|
-
const res = await fetch(STUDENT_FEE_API_ROUTES.BULK, {
|
|
31
|
-
method,
|
|
32
|
-
headers: {
|
|
33
|
-
"Content-Type": "application/json",
|
|
34
|
-
},
|
|
35
|
-
body: JSON.stringify({ schoolId, csvData }),
|
|
36
|
-
signal,
|
|
37
|
-
});
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
const errorData = (await res.json().catch(() => ({})));
|
|
40
|
-
throw new Error(errorData.error || `Bulk operation failed with status ${res.status}`);
|
|
41
|
-
}
|
|
42
|
-
const data = (await res.json());
|
|
43
|
-
return data.jobId;
|
|
44
|
-
}
|
|
45
|
-
async function pollBulkJob(jobId, signal, onProgress) {
|
|
17
|
+
async function pollJob(statusUrl, signal, onProgress) {
|
|
46
18
|
const startTime = Date.now();
|
|
47
19
|
let lastProgressToast = 0;
|
|
48
20
|
while (true) {
|
|
49
21
|
if (signal.aborted)
|
|
50
22
|
throw new Error("Polling cancelled");
|
|
51
23
|
if (Date.now() - startTime > POLL_TIMEOUT_MS) {
|
|
52
|
-
throw new Error("
|
|
24
|
+
throw new Error("Operation timed out");
|
|
53
25
|
}
|
|
54
26
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
55
27
|
if (signal.aborted)
|
|
56
28
|
throw new Error("Polling cancelled");
|
|
57
|
-
const res = await fetch(
|
|
58
|
-
signal,
|
|
59
|
-
});
|
|
29
|
+
const res = await fetch(statusUrl, { signal });
|
|
60
30
|
if (!res.ok)
|
|
61
31
|
continue;
|
|
62
32
|
const data = (await res.json());
|
|
@@ -76,127 +46,127 @@ t, errors) {
|
|
|
76
46
|
return "";
|
|
77
47
|
const lines = errors
|
|
78
48
|
.slice(0, 5)
|
|
79
|
-
.map((e) => t("
|
|
49
|
+
.map((e) => t("messagesAutoPostRowError", {
|
|
50
|
+
computerNumber: e.computerNumber,
|
|
51
|
+
error: e.error,
|
|
52
|
+
}));
|
|
80
53
|
const remaining = errors.length - 5;
|
|
81
54
|
if (remaining > 0)
|
|
82
|
-
lines.push(t("
|
|
55
|
+
lines.push(t("messagesAutoPostMoreRows", { count: remaining }));
|
|
83
56
|
return lines.join("\n");
|
|
84
57
|
}
|
|
85
58
|
export const StudentFeeMoreActions = () => {
|
|
86
59
|
const t = useTranslations("studentFee");
|
|
87
60
|
const { dispatch, handleCreateMonthly, handleCreateSingleEntry } = useStudentFeeModule();
|
|
88
61
|
const abortRef = useRef(null);
|
|
62
|
+
const [autoPostDate, setAutoPostDate] = useState("");
|
|
63
|
+
const [computerNumbersText, setComputerNumbersText] = useState("");
|
|
64
|
+
const [submitting, setSubmitting] = useState(false);
|
|
89
65
|
useEffect(() => {
|
|
90
66
|
return () => {
|
|
91
67
|
var _a;
|
|
92
68
|
(_a = abortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
93
69
|
};
|
|
94
70
|
}, []);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
71
|
+
const closeDrawer = useCallback(() => {
|
|
72
|
+
dispatch({
|
|
73
|
+
type: STUDENT_FEE_ACTION_TYPES.SET_DRAWER,
|
|
74
|
+
payload: { drawer: null },
|
|
75
|
+
});
|
|
76
|
+
}, [dispatch]);
|
|
77
|
+
const refreshList = useCallback(() => {
|
|
78
|
+
var _a;
|
|
79
|
+
const schoolId = ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "";
|
|
80
|
+
fetch(`${STUDENT_FEE_API_ROUTES.UNIT}?currentPage=1&pageLimit=${PAGE_LIMIT}&schoolId=${schoolId}`, {
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
.then(async (res) => {
|
|
86
|
+
var _a, _b;
|
|
87
|
+
if (!res.ok)
|
|
88
|
+
return;
|
|
89
|
+
const data = await res.json();
|
|
98
90
|
dispatch({
|
|
99
|
-
type: STUDENT_FEE_ACTION_TYPES.
|
|
100
|
-
payload: {
|
|
91
|
+
type: STUDENT_FEE_ACTION_TYPES.SET_ITEMS,
|
|
92
|
+
payload: {
|
|
93
|
+
items: (_a = data.items) !== null && _a !== void 0 ? _a : [],
|
|
94
|
+
count: (_b = data.count) !== null && _b !== void 0 ? _b : 0,
|
|
95
|
+
},
|
|
101
96
|
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
})
|
|
98
|
+
.catch(() => { });
|
|
99
|
+
}, [dispatch]);
|
|
100
|
+
const handleAutoPost = useCallback(async () => {
|
|
101
|
+
var _a, _b, _c, _d;
|
|
102
|
+
const schoolId = ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "";
|
|
103
|
+
const numbers = computerNumbersText
|
|
104
|
+
.split(",")
|
|
105
|
+
.map((n) => n.trim())
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
const uniqueNumbers = [...new Set(numbers)];
|
|
108
|
+
if (!autoPostDate) {
|
|
109
|
+
showErrorToast(t("validationAutoPostDateRequired"));
|
|
113
110
|
return;
|
|
114
111
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
for (let i = 0; i < records.length; i++) {
|
|
118
|
-
const row = records[i];
|
|
119
|
-
const msgs = [];
|
|
120
|
-
if (isEmpty(row.id))
|
|
121
|
-
msgs.push(t("validationRequiredIdForUpdate"));
|
|
122
|
-
if (msgs.length > 0) {
|
|
123
|
-
validationErrors.push({ row: i + 1, messages: msgs });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (validationErrors.length > 0) {
|
|
127
|
-
const errorsList = validationErrors
|
|
128
|
-
.slice(0, 5)
|
|
129
|
-
.map((e) => t("messagesBulkRowError", {
|
|
130
|
-
row: e.row,
|
|
131
|
-
error: e.messages.join("; "),
|
|
132
|
-
}))
|
|
133
|
-
.join("\n");
|
|
134
|
-
const remaining = validationErrors.length - 5;
|
|
135
|
-
const errorsStr = remaining > 0
|
|
136
|
-
? `${errorsList}\n${t("messagesBulkMoreRows", { count: remaining })}`
|
|
137
|
-
: errorsList;
|
|
138
|
-
showErrorToast(t("messagesBulkValidationFailed", {
|
|
139
|
-
count: validationErrors.length,
|
|
140
|
-
errors: errorsStr,
|
|
141
|
-
}));
|
|
112
|
+
if (uniqueNumbers.length === 0) {
|
|
113
|
+
showErrorToast(t("validationAutoPostComputerNumbersRequired"));
|
|
142
114
|
return;
|
|
143
115
|
}
|
|
116
|
+
setSubmitting(true);
|
|
117
|
+
(_b = abortRef.current) === null || _b === void 0 ? void 0 : _b.abort();
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
abortRef.current = controller;
|
|
120
|
+
const { signal } = controller;
|
|
144
121
|
try {
|
|
145
|
-
showInfoToast(t("
|
|
122
|
+
showInfoToast(t("messagesAutoPostJobQueued", { count: uniqueNumbers.length }));
|
|
146
123
|
let jobId;
|
|
147
124
|
try {
|
|
148
|
-
|
|
125
|
+
const res = await fetch(STUDENT_FEE_API_ROUTES.AUTO_POST, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
"Content-Type": "application/json",
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
schoolId,
|
|
132
|
+
computerNumbers: uniqueNumbers,
|
|
133
|
+
paidAt: autoPostDate,
|
|
134
|
+
}),
|
|
135
|
+
signal,
|
|
136
|
+
});
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
const errorData = (await res.json().catch(() => ({})));
|
|
139
|
+
throw new Error(errorData.error || `Auto-post failed with status ${res.status}`);
|
|
140
|
+
}
|
|
141
|
+
const data = (await res.json());
|
|
142
|
+
jobId = data.jobId;
|
|
149
143
|
}
|
|
150
144
|
catch (submitError) {
|
|
151
|
-
showErrorToast(t("
|
|
152
|
-
action: label,
|
|
145
|
+
showErrorToast(t("messagesAutoPostJobSubmitFailed", {
|
|
153
146
|
error: submitError instanceof Error
|
|
154
147
|
? submitError.message
|
|
155
148
|
: t("unknownError"),
|
|
156
149
|
}));
|
|
157
150
|
return;
|
|
158
151
|
}
|
|
159
|
-
const status = await
|
|
160
|
-
showInfoToast(t("
|
|
152
|
+
const status = await pollJob(STUDENT_FEE_API_ROUTES.AUTO_POST_STATUS(jobId), signal, (processed, total) => {
|
|
153
|
+
showInfoToast(t("messagesAutoPostProgress", { processed, total }));
|
|
161
154
|
});
|
|
162
155
|
if (signal.aborted)
|
|
163
156
|
return;
|
|
164
157
|
if (status.status === "completed") {
|
|
165
158
|
const r = status.results;
|
|
166
|
-
if (r && ((
|
|
159
|
+
if (r && ((_c = r.errors) === null || _c === void 0 ? void 0 : _c.length) > 0) {
|
|
167
160
|
const summary = formatErrorSummary(t, r.errors);
|
|
168
|
-
showSuccessToast(`${t("
|
|
161
|
+
showSuccessToast(`${t("messagesAutoPostResults", { paid: r.paid, skipped: r.skipped })}\n${summary}`);
|
|
169
162
|
}
|
|
170
163
|
else if (r) {
|
|
171
|
-
showSuccessToast(t("
|
|
172
|
-
created: r.created,
|
|
173
|
-
updated: r.updated,
|
|
174
|
-
skipped: r.skipped,
|
|
175
|
-
}));
|
|
164
|
+
showSuccessToast(t("messagesAutoPostResults", { paid: r.paid, skipped: r.skipped }));
|
|
176
165
|
}
|
|
177
166
|
else {
|
|
178
|
-
showSuccessToast(t("
|
|
167
|
+
showSuccessToast(t("messagesAutoPostSuccess"));
|
|
179
168
|
}
|
|
180
|
-
|
|
181
|
-
fetch(`${STUDENT_FEE_API_ROUTES.UNIT}?currentPage=1&pageLimit=${PAGE_LIMIT}&schoolId=${schoolId}`, {
|
|
182
|
-
headers: {
|
|
183
|
-
"Content-Type": "application/json",
|
|
184
|
-
},
|
|
185
|
-
})
|
|
186
|
-
.then(async (res) => {
|
|
187
|
-
var _a, _b;
|
|
188
|
-
if (!res.ok)
|
|
189
|
-
return;
|
|
190
|
-
const data = await res.json();
|
|
191
|
-
dispatch({
|
|
192
|
-
type: STUDENT_FEE_ACTION_TYPES.SET_ITEMS,
|
|
193
|
-
payload: {
|
|
194
|
-
items: (_a = data.items) !== null && _a !== void 0 ? _a : [],
|
|
195
|
-
count: (_b = data.count) !== null && _b !== void 0 ? _b : 0,
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
})
|
|
199
|
-
.catch(() => { });
|
|
169
|
+
refreshList();
|
|
200
170
|
closeDrawer();
|
|
201
171
|
}
|
|
202
172
|
else {
|
|
@@ -204,33 +174,22 @@ export const StudentFeeMoreActions = () => {
|
|
|
204
174
|
const detail = ((_d = r === null || r === void 0 ? void 0 : r.errors) === null || _d === void 0 ? void 0 : _d.length)
|
|
205
175
|
? formatErrorSummary(t, r.errors)
|
|
206
176
|
: t("unknownError");
|
|
207
|
-
showErrorToast(`${t("
|
|
177
|
+
showErrorToast(`${t("messagesAutoPostFailed")}\n${detail}`);
|
|
208
178
|
}
|
|
209
179
|
}
|
|
210
180
|
catch (error) {
|
|
211
181
|
if (error.message === "Polling cancelled")
|
|
212
182
|
return;
|
|
213
|
-
showErrorToast(t("
|
|
214
|
-
action: label,
|
|
183
|
+
showErrorToast(t("messagesAutoPostFailedDetail", {
|
|
215
184
|
error: error instanceof Error ? error.message : t("unknownError"),
|
|
216
185
|
}));
|
|
217
186
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
await handleGetAllRecords(((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "", PAGE_LIMIT);
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
{ id: "2", title: t("updateYourDataInTheCsv") },
|
|
230
|
-
{
|
|
231
|
-
id: "3",
|
|
232
|
-
title: t("uploadTheCompletedCsvToTheSystem"),
|
|
233
|
-
},
|
|
234
|
-
];
|
|
235
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Button, { disabled: new Date().getDate() !== 1, onClick: handleCreateMonthly, children: t("actionsButtonCreateMonthly") }), _jsx(Button, { onClick: handleCreateSingleEntry, children: t("actionsButtonSingleEntry") }), _jsx(Separator, {}), _jsx(Timeline, { events: update, heading: t("bulkUpdate"), handleOnBulkCreate: handleBulkUpdate })] }));
|
|
187
|
+
finally {
|
|
188
|
+
setSubmitting(false);
|
|
189
|
+
}
|
|
190
|
+
}, [t, dispatch, closeDrawer, refreshList, computerNumbersText, autoPostDate]);
|
|
191
|
+
const canSubmit = autoPostDate.trim() !== "" &&
|
|
192
|
+
computerNumbersText.trim() !== "" &&
|
|
193
|
+
!submitting;
|
|
194
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Button, { disabled: new Date().getDate() !== 1, onClick: handleCreateMonthly, children: t("actionsButtonCreateMonthly") }), _jsx(Button, { onClick: handleCreateSingleEntry, children: t("actionsButtonSingleEntry") }), _jsx(Separator, {}), _jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedInput, { id: "autoPostDate", info: t("formAutoPostDateInfo"), label: t("formAutoPostDateLabel"), onChange: (e) => setAutoPostDate(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: autoPostDate }), _jsx(EnhancedTextarea, { id: "autoPostComputerNumbers", info: t("formAutoPostComputerNumbersInfo"), label: t("formAutoPostComputerNumbersLabel"), onChange: (e) => setComputerNumbersText(e.target.value), placeholder: t("formAutoPostComputerNumbersPlaceholder"), rows: 8, value: computerNumbersText }), _jsx(Button, { disabled: !canSubmit, onClick: handleAutoPost, children: t("actionsButtonAutoPost") })] })] }));
|
|
236
195
|
};
|
package/constants.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ export declare const STUDENT_FEE_API_ROUTES: {
|
|
|
81
81
|
readonly CREATE_MONTHLY: "/api/v1/student-fee/create-monthly";
|
|
82
82
|
readonly BY_COMPUTER_NUMBER: "/api/v1/student-fee-by-student-computer-number";
|
|
83
83
|
readonly SINGLE_ENTRY: "/api/v1/student-fee-single-entry";
|
|
84
|
+
readonly AUTO_POST: "/api/v1/student-fee/auto-post";
|
|
85
|
+
readonly AUTO_POST_STATUS: (jobId: string) => string;
|
|
84
86
|
};
|
|
85
87
|
export declare const STUDENT_PROFILE_API_ROUTES: {
|
|
86
88
|
readonly LIST: "/api/v1/student-profile";
|
package/constants.js
CHANGED
|
@@ -92,6 +92,8 @@ export const STUDENT_FEE_API_ROUTES = {
|
|
|
92
92
|
CREATE_MONTHLY: "/api/v1/student-fee/create-monthly",
|
|
93
93
|
BY_COMPUTER_NUMBER: "/api/v1/student-fee-by-student-computer-number",
|
|
94
94
|
SINGLE_ENTRY: "/api/v1/student-fee-single-entry",
|
|
95
|
+
AUTO_POST: "/api/v1/student-fee/auto-post",
|
|
96
|
+
AUTO_POST_STATUS: (jobId) => `/api/v1/student-fee/auto-post/${jobId}`,
|
|
95
97
|
};
|
|
96
98
|
export const STUDENT_PROFILE_API_ROUTES = {
|
|
97
99
|
LIST: "/api/v1/student-profile",
|