@appcorp/fusion-storybook 0.2.23 → 0.2.25
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.
|
@@ -10,13 +10,17 @@ import { CSVS } from "../../constants";
|
|
|
10
10
|
import { teacherFormValidation } from "./validate";
|
|
11
11
|
import { TEACHER_API_ROUTES } from "./constants";
|
|
12
12
|
import { invalidateTeachersCache } from "./cache";
|
|
13
|
-
import {
|
|
13
|
+
import { TEACHER_ACTION_TYPES, useTeacherModule } from "./context";
|
|
14
14
|
import { useRef, useEffect, useCallback } from "react";
|
|
15
15
|
const workspace = getCachedWorkspaceSync();
|
|
16
16
|
const POLL_INTERVAL_MS = 2000;
|
|
17
17
|
const POLL_TIMEOUT_MS = 300000;
|
|
18
18
|
const handleGetAllRecords = async (schoolId) => {
|
|
19
|
-
const response = await fetch(`${TEACHER_API_ROUTES.UNIT}?pageLimit=1000¤tPage=1&schoolId=${schoolId}
|
|
19
|
+
const response = await fetch(`${TEACHER_API_ROUTES.UNIT}?pageLimit=1000¤tPage=1&schoolId=${schoolId}`, {
|
|
20
|
+
headers: {
|
|
21
|
+
"x-api-token": process.env.NEXT_PUBLIC_API_KEY,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
20
24
|
const result = await response.json();
|
|
21
25
|
const csv = await converter.json2csv(result.items || [], {});
|
|
22
26
|
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
@@ -27,11 +31,14 @@ async function submitBulkJob(csvData, method, signal) {
|
|
|
27
31
|
const schoolId = ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "";
|
|
28
32
|
if (!schoolId)
|
|
29
33
|
throw new Error("School ID not found");
|
|
34
|
+
const apiKey = process.env.NEXT_PUBLIC_API_KEY;
|
|
35
|
+
if (!apiKey)
|
|
36
|
+
throw new Error("API key not configured");
|
|
30
37
|
const res = await fetch(TEACHER_API_ROUTES.BULK, {
|
|
31
38
|
method,
|
|
32
39
|
headers: {
|
|
33
40
|
"Content-Type": "application/json",
|
|
34
|
-
"x-api-token":
|
|
41
|
+
"x-api-token": apiKey,
|
|
35
42
|
},
|
|
36
43
|
body: JSON.stringify({ schoolId, csvData }),
|
|
37
44
|
signal,
|
|
@@ -43,8 +50,9 @@ async function submitBulkJob(csvData, method, signal) {
|
|
|
43
50
|
const data = (await res.json());
|
|
44
51
|
return data.jobId;
|
|
45
52
|
}
|
|
46
|
-
async function pollBulkJob(jobId, signal) {
|
|
53
|
+
async function pollBulkJob(jobId, signal, onProgress) {
|
|
47
54
|
const startTime = Date.now();
|
|
55
|
+
let lastProgressToast = 0;
|
|
48
56
|
while (true) {
|
|
49
57
|
if (signal.aborted)
|
|
50
58
|
throw new Error("Polling cancelled");
|
|
@@ -54,9 +62,12 @@ async function pollBulkJob(jobId, signal) {
|
|
|
54
62
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
55
63
|
if (signal.aborted)
|
|
56
64
|
throw new Error("Polling cancelled");
|
|
65
|
+
const apiKey = process.env.NEXT_PUBLIC_API_KEY;
|
|
66
|
+
if (!apiKey)
|
|
67
|
+
throw new Error("API key not configured");
|
|
57
68
|
const res = await fetch(TEACHER_API_ROUTES.BULK_STATUS(jobId), {
|
|
58
69
|
headers: {
|
|
59
|
-
"x-api-token":
|
|
70
|
+
"x-api-token": apiKey,
|
|
60
71
|
},
|
|
61
72
|
signal,
|
|
62
73
|
});
|
|
@@ -66,6 +77,11 @@ async function pollBulkJob(jobId, signal) {
|
|
|
66
77
|
if (data.status === "completed" || data.status === "failed") {
|
|
67
78
|
return data;
|
|
68
79
|
}
|
|
80
|
+
// Show progress toast every 10 seconds while running
|
|
81
|
+
if (data.status === "running" && Date.now() - lastProgressToast > 10000) {
|
|
82
|
+
lastProgressToast = Date.now();
|
|
83
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress(data.processed, data.total);
|
|
84
|
+
}
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
87
|
function formatErrorSummary(errors) {
|
|
@@ -79,7 +95,7 @@ function formatErrorSummary(errors) {
|
|
|
79
95
|
}
|
|
80
96
|
export const TeacherMoreActions = () => {
|
|
81
97
|
const t = useTranslations("teacher");
|
|
82
|
-
const { dispatch } =
|
|
98
|
+
const { dispatch, listFetchNow } = useTeacherModule();
|
|
83
99
|
const abortRef = useRef(null);
|
|
84
100
|
// Abort any in-flight polling on unmount
|
|
85
101
|
useEffect(() => {
|
|
@@ -88,14 +104,14 @@ export const TeacherMoreActions = () => {
|
|
|
88
104
|
(_a = abortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
89
105
|
};
|
|
90
106
|
}, []);
|
|
91
|
-
const closeDrawer = () => {
|
|
92
|
-
dispatch({
|
|
93
|
-
type: TEACHER_ACTION_TYPES.SET_DRAWER,
|
|
94
|
-
payload: { drawer: null },
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
107
|
const handleBulkFlow = useCallback(async (files, method) => {
|
|
98
108
|
var _a, _b, _c;
|
|
109
|
+
const closeDrawer = () => {
|
|
110
|
+
dispatch({
|
|
111
|
+
type: TEACHER_ACTION_TYPES.SET_DRAWER,
|
|
112
|
+
payload: { drawer: null },
|
|
113
|
+
});
|
|
114
|
+
};
|
|
99
115
|
// Cancel previous in-flight request
|
|
100
116
|
(_a = abortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
101
117
|
const controller = new AbortController();
|
|
@@ -132,8 +148,17 @@ export const TeacherMoreActions = () => {
|
|
|
132
148
|
}
|
|
133
149
|
try {
|
|
134
150
|
showInfoToast(`Bulk ${label} job queued (${records.length} records). Processing...`);
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
let jobId;
|
|
152
|
+
try {
|
|
153
|
+
jobId = await submitBulkJob(text, method, signal);
|
|
154
|
+
}
|
|
155
|
+
catch (submitError) {
|
|
156
|
+
showErrorToast(`Failed to submit ${label} job: ${submitError instanceof Error ? submitError.message : "Unknown error"}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const status = await pollBulkJob(jobId, signal, (processed, total) => {
|
|
160
|
+
showInfoToast(`Processing ${processed}/${total} teachers...`);
|
|
161
|
+
});
|
|
137
162
|
if (signal.aborted)
|
|
138
163
|
return;
|
|
139
164
|
if (status.status === "completed") {
|
|
@@ -149,6 +174,7 @@ export const TeacherMoreActions = () => {
|
|
|
149
174
|
showSuccessToast("Bulk operation completed successfully");
|
|
150
175
|
}
|
|
151
176
|
invalidateTeachersCache();
|
|
177
|
+
listFetchNow();
|
|
152
178
|
closeDrawer();
|
|
153
179
|
}
|
|
154
180
|
else {
|
|
@@ -164,7 +190,7 @@ export const TeacherMoreActions = () => {
|
|
|
164
190
|
return;
|
|
165
191
|
showErrorToast(`Bulk ${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
166
192
|
}
|
|
167
|
-
}, [
|
|
193
|
+
}, [dispatch, listFetchNow]);
|
|
168
194
|
const handleBulkCreate = useCallback((files) => handleBulkFlow(files, "POST"), [handleBulkFlow]);
|
|
169
195
|
const handleBulkUpdate = useCallback((files) => handleBulkFlow(files, "PUT"), [handleBulkFlow]);
|
|
170
196
|
const create = [
|
package/components/timeline.js
CHANGED
|
@@ -9,7 +9,14 @@ export const Timeline = ({ heading, events, handleOnBulkCreate, }) => {
|
|
|
9
9
|
// EnhancedDropzone calls onChange synchronously during render.
|
|
10
10
|
queueMicrotask(() => setFiles(f));
|
|
11
11
|
}, []);
|
|
12
|
+
const handleUploadClick = useCallback(() => {
|
|
13
|
+
if (files.length > 0) {
|
|
14
|
+
handleOnBulkCreate === null || handleOnBulkCreate === void 0 ? void 0 : handleOnBulkCreate(files);
|
|
15
|
+
// Clear files after upload to allow resubmission
|
|
16
|
+
queueMicrotask(() => setFiles([]));
|
|
17
|
+
}
|
|
18
|
+
}, [files, handleOnBulkCreate]);
|
|
12
19
|
return (_jsxs("div", { className: "space-y-6", children: [_jsx("h2", { className: "text-foreground text-xl font-semibold", children: heading }), _jsxs("div", { className: "space-y-4", children: [events.map((event) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "z-10 flex h-6 w-6 items-center justify-center", children: _jsx("div", { className: "bg-muted-foreground h-3 w-3 rounded-full" }) }), _jsxs("div", { className: "min-w-0 flex-1 pb-1", children: [_jsx("div", { onClick: event.handleOnClick, className: `text-muted-foreground text-sm ${event.handleOnClick ? "cursor-pointer underline" : ""}`, children: event.title }), event.description && (_jsx("div", { className: "text-foreground mt-1 text-sm", children: event.description }))] })] }, event.id))), _jsx(EnhancedDropzone, { className: "", label: "Upload CSV", id: "timeline-dropzone", info: "Drag and drop your CSV file here, or click to select.", error: "", accept: { "text/csv": [".csv"] }, maxFiles: 1, onChange: handleDropzoneChange, onRemoveRemote: () => {
|
|
13
20
|
// Handle remote file removal if needed
|
|
14
|
-
} }), _jsx(Button, { disabled: files.length === 0, onClick:
|
|
21
|
+
} }), _jsx(Button, { disabled: files.length === 0, onClick: handleUploadClick, children: "Upload and Process CSV" })] })] }));
|
|
15
22
|
};
|