@appcorp/fusion-storybook 0.2.17 → 0.2.19
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.
|
@@ -14,9 +14,9 @@ export const pageLimit = Number(process.env.NEXT_PUBLIC_PAGE_LIMIT) || 10;
|
|
|
14
14
|
// API ROUTES
|
|
15
15
|
// ============================================================================
|
|
16
16
|
export const TEACHER_API_ROUTES = {
|
|
17
|
-
LIST: "/api/v1/teacher",
|
|
18
17
|
UNIT: "/api/v1/teacher",
|
|
19
18
|
AVATAR: "/api/v1/teacher-avatar",
|
|
19
|
+
BULK: "/api/v1/teacher/bulk",
|
|
20
20
|
};
|
|
21
21
|
// ============================================================================
|
|
22
22
|
// GENDER OPTIONS
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { downloadFromUrl } from "@react-pakistan/util-functions";
|
|
4
4
|
import { showErrorToast, showSuccessToast, } from "@appcorp/shadcn/lib/toast-utils";
|
|
5
5
|
import { getCachedWorkspaceSync } from "../workspace/cache";
|
|
@@ -8,73 +8,87 @@ import { Timeline } from "../../components/timeline";
|
|
|
8
8
|
import { useTranslations } from "next-intl";
|
|
9
9
|
import { CSVS } from "../../constants";
|
|
10
10
|
import { teacherFormValidation } from "./validate";
|
|
11
|
+
import { TEACHER_API_ROUTES } from "./constants";
|
|
11
12
|
const workspace = getCachedWorkspaceSync();
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const handleGetAllRecords = async (schoolId) => {
|
|
14
|
+
const response = await fetch(`/${TEACHER_API_ROUTES.UNIT}?pageLimit=1000¤tPage=1&schoolId=${schoolId}`);
|
|
15
|
+
const result = await response.json();
|
|
16
|
+
const csv = await converter.json2csv(result.items || [], {});
|
|
17
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
18
|
+
await downloadFromUrl(blob, "teacher.csv");
|
|
19
|
+
};
|
|
20
|
+
async function processBulkCsv(files, method) {
|
|
21
|
+
var _a;
|
|
22
|
+
if (!files || files.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
const file = files[0];
|
|
25
|
+
try {
|
|
26
|
+
const text = await file.text();
|
|
27
|
+
const records = converter.csv2json(text);
|
|
28
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
29
|
+
showErrorToast("CSV file is empty or invalid");
|
|
17
30
|
return;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < records.length; i++) {
|
|
29
|
-
const result = teacherFormValidation.safeParse(records[i]);
|
|
30
|
-
if (!result.success) {
|
|
31
|
-
errors.push({
|
|
32
|
-
row: i + 1,
|
|
33
|
-
messages: result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
validRecords.push(result.data);
|
|
38
|
-
}
|
|
31
|
+
}
|
|
32
|
+
const errors = [];
|
|
33
|
+
const validRecords = [];
|
|
34
|
+
for (let i = 0; i < records.length; i++) {
|
|
35
|
+
const result = teacherFormValidation.safeParse(records[i]);
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
errors.push({
|
|
38
|
+
row: i + 1,
|
|
39
|
+
messages: result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`),
|
|
40
|
+
});
|
|
39
41
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.slice(0, 5)
|
|
43
|
-
.map((e) => `Row ${e.row}: ${e.messages.join("; ")}`)
|
|
44
|
-
.join("\n");
|
|
45
|
-
const remaining = errors.length - 5;
|
|
46
|
-
showErrorToast(`Validation failed for ${errors.length} row(s).\n${summary}${remaining > 0 ? `\n...and ${remaining} more` : ""}`);
|
|
47
|
-
return;
|
|
42
|
+
else {
|
|
43
|
+
validRecords.push(result.data);
|
|
48
44
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
45
|
+
}
|
|
46
|
+
if (errors.length > 0) {
|
|
47
|
+
const summary = errors
|
|
48
|
+
.slice(0, 5)
|
|
49
|
+
.map((e) => `Row ${e.row}: ${e.messages.join("; ")}`)
|
|
50
|
+
.join("\n");
|
|
51
|
+
const remaining = errors.length - 5;
|
|
52
|
+
showErrorToast(`Validation failed for ${errors.length} row(s).\n${summary}${remaining > 0 ? `\n...and ${remaining} more` : ""}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const schoolId = ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "";
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(TEACHER_API_ROUTES.BULK, {
|
|
58
|
+
method,
|
|
59
|
+
headers: {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"x-api-token": process.env.NEXT_PUBLIC_API_KEY,
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
schoolId,
|
|
65
|
+
csvData: text,
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
if (response.ok) {
|
|
69
|
+
showSuccessToast(`Successfully processed ${validRecords.length} teacher(s)`);
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
else {
|
|
72
|
+
const errorData = await response.json().catch(() => ({}));
|
|
73
|
+
showErrorToast(errorData.error ||
|
|
74
|
+
`Bulk operation failed with status ${response.status}`);
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
|
-
catch (
|
|
76
|
-
showErrorToast(
|
|
77
|
+
catch (_b) {
|
|
78
|
+
showErrorToast("Network error during bulk operation");
|
|
77
79
|
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
showErrorToast(`Failed to process CSV: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export const TeacherMoreActions = () => {
|
|
86
|
+
const t = useTranslations("teacher");
|
|
87
|
+
const handleBulkCreate = async (files) => {
|
|
88
|
+
await processBulkCsv(files, "POST");
|
|
89
|
+
};
|
|
90
|
+
const handleBulkUpdate = async (files) => {
|
|
91
|
+
await processBulkCsv(files, "PUT");
|
|
78
92
|
};
|
|
79
93
|
const create = [
|
|
80
94
|
{
|
|
@@ -90,5 +104,20 @@ export const TeacherMoreActions = () => {
|
|
|
90
104
|
title: t("moreActionsUploadTheCompletedCsvToTheSystem"),
|
|
91
105
|
},
|
|
92
106
|
];
|
|
93
|
-
|
|
107
|
+
const update = [
|
|
108
|
+
{
|
|
109
|
+
id: "1",
|
|
110
|
+
title: t("moreActionsDownloadPopulatedCsvTemplate"),
|
|
111
|
+
handleOnClick: async () => {
|
|
112
|
+
var _a;
|
|
113
|
+
await handleGetAllRecords(((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.id) || "");
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{ id: "2", title: t("moreActionsUpdateYourDataToTheCsv") },
|
|
117
|
+
{
|
|
118
|
+
id: "3",
|
|
119
|
+
title: t("moreActionsUploadTheCompletedCsvToTheSystem"),
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Timeline, { events: create, heading: t("moreActionsBulkCreate"), handleOnBulkCreate: handleBulkCreate }), _jsx(Timeline, { events: update, heading: t("moreActionsBulkUpdate"), handleOnBulkCreate: handleBulkUpdate })] }));
|
|
94
123
|
};
|
package/components/timeline.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useState, useCallback } from "react";
|
|
3
3
|
import { EnhancedDropzone } from "@appcorp/shadcn/components/enhanced-dropzone";
|
|
4
4
|
import { Button } from "@appcorp/shadcn/components/ui/button";
|
|
5
5
|
export const Timeline = ({ heading, events, handleOnBulkCreate, }) => {
|
|
6
6
|
const [files, setFiles] = useState([]);
|
|
7
|
-
|
|
7
|
+
const handleDropzoneChange = useCallback((f) => {
|
|
8
|
+
// Defer state update to avoid "setState during render" if
|
|
9
|
+
// EnhancedDropzone calls onChange synchronously during render.
|
|
10
|
+
queueMicrotask(() => setFiles(f));
|
|
11
|
+
}, []);
|
|
12
|
+
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: () => {
|
|
8
13
|
// Handle remote file removal if needed
|
|
9
14
|
} }), _jsx(Button, { disabled: files.length === 0, onClick: () => handleOnBulkCreate === null || handleOnBulkCreate === void 0 ? void 0 : handleOnBulkCreate(files), children: "Upload and Process CSV" })] })] }));
|
|
10
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appcorp/fusion-storybook",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build-storybook": "storybook build",
|
|
6
6
|
"build:next": "next build",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@appcorp/app-corp-vista": "^1.0.0",
|
|
41
|
-
"@appcorp/shadcn": "^1.1.
|
|
41
|
+
"@appcorp/shadcn": "^1.1.92",
|
|
42
42
|
"@chromatic-com/storybook": "^5.1.2",
|
|
43
43
|
"@commitlint/cli": "^20.5.0",
|
|
44
44
|
"@commitlint/config-conventional": "^20.5.0",
|