xladmin-import-export 0.1.0
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/README.md +55 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +729 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="./README.md">
|
|
3
|
+
<img src="https://img.shields.io/badge/English-blue?style=for-the-badge" alt="English">
|
|
4
|
+
</a>
|
|
5
|
+
<a href="./docs/README.ru.md">
|
|
6
|
+
<img src="https://img.shields.io/badge/%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9-red?style=for-the-badge" alt="Русский">
|
|
7
|
+
</a>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
# xladmin-import-export frontend
|
|
11
|
+
|
|
12
|
+
Optional frontend extension for `xladmin` that adds import/export actions to model pages.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- export and import icon buttons for the model toolbar
|
|
17
|
+
- export dialog with format and field selection
|
|
18
|
+
- import dialog with file upload, conflict mode, validation preview, and commit
|
|
19
|
+
- works with `xladmin` selection state, including "select all current results"
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i xladmin xladmin-import-export
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Minimal Example
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import {ModelPage} from 'xladmin';
|
|
31
|
+
import {
|
|
32
|
+
ModelImportExportActions,
|
|
33
|
+
createAxiosXLAdminImportExportClient,
|
|
34
|
+
} from 'xladmin-import-export';
|
|
35
|
+
|
|
36
|
+
const importExportClient = createAxiosXLAdminImportExportClient(api);
|
|
37
|
+
|
|
38
|
+
<ModelPage
|
|
39
|
+
client={client}
|
|
40
|
+
basePath="/admin"
|
|
41
|
+
slug="users"
|
|
42
|
+
renderBeforePagination={(context) => (
|
|
43
|
+
<ModelImportExportActions client={importExportClient} context={context} />
|
|
44
|
+
)}
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd xladmin-frontend
|
|
52
|
+
npm run test --workspace ./packages/xladmin-import-export
|
|
53
|
+
npm run check --workspace ./packages/xladmin-import-export
|
|
54
|
+
npm run build --workspace ./packages/xladmin-import-export
|
|
55
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { XLAdminRequestOptions, ModelPageToolbarContext } from 'xladmin';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
type ImportExportFieldMeta = {
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
default_selected: boolean;
|
|
8
|
+
};
|
|
9
|
+
type ImportConflictMode = 'auto_generate_pk' | 'update_existing' | 'skip_existing';
|
|
10
|
+
type ImportExportFormat = 'xlsx' | 'csv' | 'json';
|
|
11
|
+
type ImportExportMetaResponse = {
|
|
12
|
+
model_slug: string;
|
|
13
|
+
export_formats: ImportExportFormat[];
|
|
14
|
+
import_formats: ImportExportFormat[];
|
|
15
|
+
export_fields: ImportExportFieldMeta[];
|
|
16
|
+
import_fields: ImportExportFieldMeta[];
|
|
17
|
+
pk_field: string;
|
|
18
|
+
pk_type: string;
|
|
19
|
+
available_conflict_modes: ImportConflictMode[];
|
|
20
|
+
};
|
|
21
|
+
type ImportValidationResponse = {
|
|
22
|
+
summary: {
|
|
23
|
+
total_rows: number;
|
|
24
|
+
create: number;
|
|
25
|
+
update: number;
|
|
26
|
+
skip: number;
|
|
27
|
+
errors: number;
|
|
28
|
+
};
|
|
29
|
+
created_preview: Array<{
|
|
30
|
+
row_number: number;
|
|
31
|
+
label: string;
|
|
32
|
+
}>;
|
|
33
|
+
updated_preview: Array<{
|
|
34
|
+
row_number: number;
|
|
35
|
+
label: string;
|
|
36
|
+
}>;
|
|
37
|
+
skipped_preview: Array<{
|
|
38
|
+
row_number: number;
|
|
39
|
+
label: string;
|
|
40
|
+
}>;
|
|
41
|
+
errors: Array<{
|
|
42
|
+
row_number: number;
|
|
43
|
+
field?: string | null;
|
|
44
|
+
message: string;
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
type ImportCommitResponse = {
|
|
48
|
+
created: number;
|
|
49
|
+
updated: number;
|
|
50
|
+
skipped: number;
|
|
51
|
+
};
|
|
52
|
+
type ImportExportSelectionScope = {
|
|
53
|
+
q?: string;
|
|
54
|
+
sort?: string;
|
|
55
|
+
filters?: Record<string, string>;
|
|
56
|
+
};
|
|
57
|
+
type ExportRequestPayload = {
|
|
58
|
+
format: ImportExportFormat;
|
|
59
|
+
fields: string[];
|
|
60
|
+
ids: Array<string | number>;
|
|
61
|
+
select_all?: boolean;
|
|
62
|
+
selection_scope?: ImportExportSelectionScope;
|
|
63
|
+
};
|
|
64
|
+
type ImportRequestPayload = {
|
|
65
|
+
file: File;
|
|
66
|
+
format: ImportExportFormat;
|
|
67
|
+
fields: string[];
|
|
68
|
+
conflict_mode: ImportConflictMode;
|
|
69
|
+
};
|
|
70
|
+
type DownloadExportResponse = {
|
|
71
|
+
blob: Blob;
|
|
72
|
+
filename: string;
|
|
73
|
+
};
|
|
74
|
+
type XLAdminImportExportAxiosLike = {
|
|
75
|
+
get: <T>(url: string, config?: {
|
|
76
|
+
signal?: AbortSignal;
|
|
77
|
+
}) => Promise<{
|
|
78
|
+
data: T;
|
|
79
|
+
} | T>;
|
|
80
|
+
post: <T>(url: string, body?: unknown, config?: {
|
|
81
|
+
signal?: AbortSignal;
|
|
82
|
+
headers?: Record<string, string>;
|
|
83
|
+
responseType?: 'blob';
|
|
84
|
+
}) => Promise<{
|
|
85
|
+
data: T;
|
|
86
|
+
headers?: Record<string, string>;
|
|
87
|
+
} | T>;
|
|
88
|
+
};
|
|
89
|
+
type XLAdminImportExportClient = {
|
|
90
|
+
getMeta: (slug: string, options?: XLAdminRequestOptions) => Promise<ImportExportMetaResponse>;
|
|
91
|
+
downloadExport: (slug: string, payload: ExportRequestPayload, options?: XLAdminRequestOptions) => Promise<DownloadExportResponse>;
|
|
92
|
+
validateImport: (slug: string, payload: ImportRequestPayload, options?: XLAdminRequestOptions) => Promise<ImportValidationResponse>;
|
|
93
|
+
commitImport: (slug: string, payload: ImportRequestPayload, options?: XLAdminRequestOptions) => Promise<ImportCommitResponse>;
|
|
94
|
+
};
|
|
95
|
+
type XLAdminImportExportFetchClientConfig = {
|
|
96
|
+
baseUrl: string;
|
|
97
|
+
fetch?: typeof globalThis.fetch;
|
|
98
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
99
|
+
credentials?: RequestCredentials;
|
|
100
|
+
};
|
|
101
|
+
declare function createAxiosXLAdminImportExportClient(api: XLAdminImportExportAxiosLike): XLAdminImportExportClient;
|
|
102
|
+
declare function createFetchXLAdminImportExportClient(config: XLAdminImportExportFetchClientConfig): XLAdminImportExportClient;
|
|
103
|
+
|
|
104
|
+
type ModelImportExportActionsProps = {
|
|
105
|
+
client: XLAdminImportExportClient;
|
|
106
|
+
context: ModelPageToolbarContext;
|
|
107
|
+
};
|
|
108
|
+
declare function ModelImportExportActions({ client, context }: ModelImportExportActionsProps): react_jsx_runtime.JSX.Element | null;
|
|
109
|
+
|
|
110
|
+
export { type DownloadExportResponse, type ExportRequestPayload, type ImportCommitResponse, type ImportConflictMode, type ImportExportFieldMeta, type ImportExportFormat, type ImportExportMetaResponse, type ImportExportSelectionScope, type ImportRequestPayload, type ImportValidationResponse, ModelImportExportActions, type XLAdminImportExportAxiosLike, type XLAdminImportExportClient, type XLAdminImportExportFetchClientConfig, createAxiosXLAdminImportExportClient, createFetchXLAdminImportExportClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
function createAxiosXLAdminImportExportClient(api) {
|
|
3
|
+
return {
|
|
4
|
+
async getMeta(slug, options) {
|
|
5
|
+
return unwrap(await api.get(`/xladmin/models/${slug}/import-export/meta/`, {
|
|
6
|
+
signal: options == null ? void 0 : options.signal
|
|
7
|
+
}));
|
|
8
|
+
},
|
|
9
|
+
async downloadExport(slug, payload, options) {
|
|
10
|
+
var _a;
|
|
11
|
+
const response = await api.post(
|
|
12
|
+
`/xladmin/models/${slug}/export/`,
|
|
13
|
+
payload,
|
|
14
|
+
{
|
|
15
|
+
signal: options == null ? void 0 : options.signal,
|
|
16
|
+
responseType: "blob"
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
const headers = isWrappedResponse(response) && response.headers ? response.headers : void 0;
|
|
20
|
+
const blob = unwrap(response);
|
|
21
|
+
return {
|
|
22
|
+
blob,
|
|
23
|
+
filename: (_a = resolveFilename(headers)) != null ? _a : `${slug}-export.${payload.format}`
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
async validateImport(slug, payload, options) {
|
|
27
|
+
return unwrap(await api.post(
|
|
28
|
+
`/xladmin/models/${slug}/import/validate/`,
|
|
29
|
+
await buildImportFormData(payload),
|
|
30
|
+
{
|
|
31
|
+
signal: options == null ? void 0 : options.signal,
|
|
32
|
+
headers: {}
|
|
33
|
+
}
|
|
34
|
+
));
|
|
35
|
+
},
|
|
36
|
+
async commitImport(slug, payload, options) {
|
|
37
|
+
return unwrap(await api.post(
|
|
38
|
+
`/xladmin/models/${slug}/import/commit/`,
|
|
39
|
+
await buildImportFormData(payload),
|
|
40
|
+
{
|
|
41
|
+
signal: options == null ? void 0 : options.signal,
|
|
42
|
+
headers: {}
|
|
43
|
+
}
|
|
44
|
+
));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function createFetchXLAdminImportExportClient(config) {
|
|
49
|
+
var _a;
|
|
50
|
+
const fetchImpl = (_a = config.fetch) != null ? _a : globalThis.fetch;
|
|
51
|
+
if (!fetchImpl) {
|
|
52
|
+
throw new Error("Fetch API is not available in the current environment.");
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
async getMeta(slug, options) {
|
|
56
|
+
return await requestJson(fetchImpl, config, "GET", `/xladmin/models/${slug}/import-export/meta/`, void 0, options == null ? void 0 : options.signal);
|
|
57
|
+
},
|
|
58
|
+
async downloadExport(slug, payload, options) {
|
|
59
|
+
var _a2;
|
|
60
|
+
const response = await request(fetchImpl, config, "POST", `/xladmin/models/${slug}/export/`, payload, options == null ? void 0 : options.signal);
|
|
61
|
+
return {
|
|
62
|
+
blob: await response.blob(),
|
|
63
|
+
filename: (_a2 = resolveFilename(headersToRecord(response.headers))) != null ? _a2 : `${slug}-export.${payload.format}`
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
async validateImport(slug, payload, options) {
|
|
67
|
+
return await requestJson(
|
|
68
|
+
fetchImpl,
|
|
69
|
+
config,
|
|
70
|
+
"POST",
|
|
71
|
+
`/xladmin/models/${slug}/import/validate/`,
|
|
72
|
+
await buildImportFormData(payload),
|
|
73
|
+
options == null ? void 0 : options.signal
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
async commitImport(slug, payload, options) {
|
|
77
|
+
return await requestJson(
|
|
78
|
+
fetchImpl,
|
|
79
|
+
config,
|
|
80
|
+
"POST",
|
|
81
|
+
`/xladmin/models/${slug}/import/commit/`,
|
|
82
|
+
await buildImportFormData(payload),
|
|
83
|
+
options == null ? void 0 : options.signal
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async function buildImportFormData(payload) {
|
|
89
|
+
const formData = new FormData();
|
|
90
|
+
formData.append("file", await createStableUploadFile(payload.file), payload.file.name);
|
|
91
|
+
formData.append("format", payload.format);
|
|
92
|
+
formData.append("conflict_mode", payload.conflict_mode);
|
|
93
|
+
formData.append("fields", JSON.stringify(payload.fields));
|
|
94
|
+
return formData;
|
|
95
|
+
}
|
|
96
|
+
async function createStableUploadFile(file) {
|
|
97
|
+
const buffer = await file.arrayBuffer();
|
|
98
|
+
return new File([buffer], file.name, {
|
|
99
|
+
type: file.type,
|
|
100
|
+
lastModified: file.lastModified
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function isWrappedResponse(response) {
|
|
104
|
+
return typeof response === "object" && response !== null && "data" in response;
|
|
105
|
+
}
|
|
106
|
+
function unwrap(response) {
|
|
107
|
+
return isWrappedResponse(response) ? response.data : response;
|
|
108
|
+
}
|
|
109
|
+
function resolveFilename(headers) {
|
|
110
|
+
var _a, _b;
|
|
111
|
+
const contentDisposition = (_a = headers == null ? void 0 : headers["content-disposition"]) != null ? _a : headers == null ? void 0 : headers["Content-Disposition"];
|
|
112
|
+
if (!contentDisposition) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const match = /filename="([^"]+)"/.exec(contentDisposition);
|
|
116
|
+
return (_b = match == null ? void 0 : match[1]) != null ? _b : null;
|
|
117
|
+
}
|
|
118
|
+
function headersToRecord(headers) {
|
|
119
|
+
const normalizedHeaders = {};
|
|
120
|
+
headers.forEach((value, key) => {
|
|
121
|
+
normalizedHeaders[key] = value;
|
|
122
|
+
});
|
|
123
|
+
return normalizedHeaders;
|
|
124
|
+
}
|
|
125
|
+
async function requestJson(fetchImpl, config, method, path, body, signal) {
|
|
126
|
+
const response = await request(fetchImpl, config, method, path, body, signal);
|
|
127
|
+
return await response.json();
|
|
128
|
+
}
|
|
129
|
+
async function request(fetchImpl, config, method, path, body, signal) {
|
|
130
|
+
var _a;
|
|
131
|
+
const headers = await resolveHeaders(config.headers);
|
|
132
|
+
const isFormData = body instanceof FormData;
|
|
133
|
+
const response = await fetchImpl(buildRequestUrl(config.baseUrl, path), {
|
|
134
|
+
method,
|
|
135
|
+
signal,
|
|
136
|
+
credentials: (_a = config.credentials) != null ? _a : "include",
|
|
137
|
+
headers: isFormData ? headers : {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
...headers
|
|
140
|
+
},
|
|
141
|
+
body: body === void 0 ? void 0 : isFormData ? body : JSON.stringify(body)
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
let detail = `${response.status} ${response.statusText}`;
|
|
145
|
+
try {
|
|
146
|
+
const errorData = await response.json();
|
|
147
|
+
if (typeof errorData.detail === "string" && errorData.detail) {
|
|
148
|
+
detail = errorData.detail;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
const fallbackText = await response.text().catch(() => "");
|
|
152
|
+
if (fallbackText.trim()) {
|
|
153
|
+
detail = fallbackText.trim();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw new Error(detail);
|
|
157
|
+
}
|
|
158
|
+
return response;
|
|
159
|
+
}
|
|
160
|
+
async function resolveHeaders(headers) {
|
|
161
|
+
if (headers === void 0) {
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
if (typeof headers === "function") {
|
|
165
|
+
return headers();
|
|
166
|
+
}
|
|
167
|
+
return headers;
|
|
168
|
+
}
|
|
169
|
+
function buildRequestUrl(baseUrl, path) {
|
|
170
|
+
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
171
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
172
|
+
if (/^https?:\/\//i.test(normalizedBaseUrl)) {
|
|
173
|
+
return `${normalizedBaseUrl}${normalizedPath}`;
|
|
174
|
+
}
|
|
175
|
+
return `${normalizedBaseUrl}${normalizedPath}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/components/ModelImportExportActions.tsx
|
|
179
|
+
import { useEffect, useMemo, useState } from "react";
|
|
180
|
+
import DownloadIcon from "@mui/icons-material/Download";
|
|
181
|
+
import UploadIcon from "@mui/icons-material/Upload";
|
|
182
|
+
import {
|
|
183
|
+
Alert,
|
|
184
|
+
Box,
|
|
185
|
+
Button,
|
|
186
|
+
Checkbox,
|
|
187
|
+
CircularProgress,
|
|
188
|
+
Dialog,
|
|
189
|
+
DialogActions,
|
|
190
|
+
DialogContent,
|
|
191
|
+
DialogTitle,
|
|
192
|
+
FormControl,
|
|
193
|
+
FormControlLabel,
|
|
194
|
+
IconButton,
|
|
195
|
+
InputLabel,
|
|
196
|
+
List,
|
|
197
|
+
ListItem,
|
|
198
|
+
MenuItem,
|
|
199
|
+
Select,
|
|
200
|
+
Stack,
|
|
201
|
+
Tooltip,
|
|
202
|
+
Typography
|
|
203
|
+
} from "@mui/material";
|
|
204
|
+
import { useAdminLocale } from "xladmin";
|
|
205
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
206
|
+
function ModelImportExportActions({ client, context }) {
|
|
207
|
+
const locale = useAdminLocale();
|
|
208
|
+
const messages = useMemo(() => getMessages(locale), [locale]);
|
|
209
|
+
const [meta, setMeta] = useState(null);
|
|
210
|
+
const [isUnavailable, setIsUnavailable] = useState(false);
|
|
211
|
+
const [error, setError] = useState(null);
|
|
212
|
+
const [exportOpen, setExportOpen] = useState(false);
|
|
213
|
+
const [importOpen, setImportOpen] = useState(false);
|
|
214
|
+
const [exportFormat, setExportFormat] = useState("xlsx");
|
|
215
|
+
const [selectedExportFields, setSelectedExportFields] = useState([]);
|
|
216
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
217
|
+
const [importFormat, setImportFormat] = useState("xlsx");
|
|
218
|
+
const [selectedImportFields, setSelectedImportFields] = useState([]);
|
|
219
|
+
const [conflictMode, setConflictMode] = useState("update_existing");
|
|
220
|
+
const [importFile, setImportFile] = useState(null);
|
|
221
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
222
|
+
const [isImporting, setIsImporting] = useState(false);
|
|
223
|
+
const [validationResult, setValidationResult] = useState(null);
|
|
224
|
+
const [validationError, setValidationError] = useState(null);
|
|
225
|
+
const [importSuccess, setImportSuccess] = useState(null);
|
|
226
|
+
const resetImportValidation = () => {
|
|
227
|
+
setValidationResult(null);
|
|
228
|
+
setValidationError(null);
|
|
229
|
+
setImportSuccess(null);
|
|
230
|
+
};
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
let isMounted = true;
|
|
233
|
+
setMeta(null);
|
|
234
|
+
setIsUnavailable(false);
|
|
235
|
+
setError(null);
|
|
236
|
+
client.getMeta(context.slug).then((response) => {
|
|
237
|
+
var _a, _b, _c;
|
|
238
|
+
if (!isMounted) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
setMeta(response);
|
|
242
|
+
setExportFormat((_a = response.export_formats[0]) != null ? _a : "xlsx");
|
|
243
|
+
setSelectedExportFields(getDefaultSelectedFields(response.export_fields));
|
|
244
|
+
setImportFormat((_b = response.import_formats[0]) != null ? _b : "xlsx");
|
|
245
|
+
setSelectedImportFields(getDefaultSelectedFields(response.import_fields));
|
|
246
|
+
setConflictMode((_c = response.available_conflict_modes[0]) != null ? _c : "update_existing");
|
|
247
|
+
}).catch((reason) => {
|
|
248
|
+
if (!isMounted) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const message = reason instanceof Error ? reason.message : "Failed to load import/export settings.";
|
|
252
|
+
if (message.includes("404")) {
|
|
253
|
+
setIsUnavailable(true);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
setError(message);
|
|
257
|
+
});
|
|
258
|
+
return () => {
|
|
259
|
+
isMounted = false;
|
|
260
|
+
};
|
|
261
|
+
}, [client, context.slug]);
|
|
262
|
+
if (isUnavailable || meta === null) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const exportCount = context.selectionCount > 0 ? context.selectionCount : context.total;
|
|
266
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
267
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", children: [
|
|
268
|
+
/* @__PURE__ */ jsx(Tooltip, { title: messages.exportButton, children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(IconButton, { size: "small", onClick: () => setExportOpen(true), disabled: Boolean(error), children: /* @__PURE__ */ jsx(DownloadIcon, { fontSize: "small" }) }) }) }),
|
|
269
|
+
/* @__PURE__ */ jsx(Tooltip, { title: messages.importButton, children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(IconButton, { size: "small", onClick: () => setImportOpen(true), disabled: Boolean(error), children: /* @__PURE__ */ jsx(UploadIcon, { fontSize: "small" }) }) }) })
|
|
270
|
+
] }),
|
|
271
|
+
/* @__PURE__ */ jsxs(Dialog, { open: exportOpen, onClose: () => !isExporting && setExportOpen(false), fullWidth: true, maxWidth: "md", children: [
|
|
272
|
+
/* @__PURE__ */ jsx(DialogTitle, { sx: { px: 3, pt: 3, pb: 1 }, children: messages.exportTitle }),
|
|
273
|
+
/* @__PURE__ */ jsx(DialogContent, { sx: { px: 3, pb: 3, pt: 0.5 }, children: /* @__PURE__ */ jsxs(Stack, { spacing: 1.5, children: [
|
|
274
|
+
error ? /* @__PURE__ */ jsx(Alert, { severity: "error", children: error }) : null,
|
|
275
|
+
/* @__PURE__ */ jsx(Typography, { color: "text.secondary", children: messages.exportCount(exportCount, context.selectionCount > 0) }),
|
|
276
|
+
/* @__PURE__ */ jsxs(FormControl, { fullWidth: true, children: [
|
|
277
|
+
/* @__PURE__ */ jsx(InputLabel, { id: "export-format-label", children: messages.formatLabel }),
|
|
278
|
+
/* @__PURE__ */ jsx(
|
|
279
|
+
Select,
|
|
280
|
+
{
|
|
281
|
+
labelId: "export-format-label",
|
|
282
|
+
value: exportFormat,
|
|
283
|
+
label: messages.formatLabel,
|
|
284
|
+
onChange: (event) => setExportFormat(event.target.value),
|
|
285
|
+
children: meta.export_formats.map((format) => /* @__PURE__ */ jsx(MenuItem, { value: format, children: format.toUpperCase() }, format))
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
] }),
|
|
289
|
+
/* @__PURE__ */ jsx(
|
|
290
|
+
FieldsSelector,
|
|
291
|
+
{
|
|
292
|
+
title: messages.exportFieldsLabel,
|
|
293
|
+
fields: meta.export_fields,
|
|
294
|
+
selectedFields: selectedExportFields,
|
|
295
|
+
onChange: setSelectedExportFields,
|
|
296
|
+
allLabel: messages.selectAllFields,
|
|
297
|
+
noneLabel: messages.clearFields
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
] }) }),
|
|
301
|
+
/* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, pb: 3, pt: 0, gap: 1 }, children: [
|
|
302
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setExportOpen(false), disabled: isExporting, children: messages.cancel }),
|
|
303
|
+
/* @__PURE__ */ jsx(
|
|
304
|
+
Button,
|
|
305
|
+
{
|
|
306
|
+
variant: "contained",
|
|
307
|
+
onClick: () => void handleExport({
|
|
308
|
+
client,
|
|
309
|
+
context,
|
|
310
|
+
format: exportFormat,
|
|
311
|
+
fields: selectedExportFields,
|
|
312
|
+
setIsExporting,
|
|
313
|
+
onDone: () => setExportOpen(false)
|
|
314
|
+
}),
|
|
315
|
+
disabled: isExporting || selectedExportFields.length === 0,
|
|
316
|
+
children: isExporting ? messages.exporting : messages.exportConfirm
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
] })
|
|
320
|
+
] }),
|
|
321
|
+
/* @__PURE__ */ jsxs(
|
|
322
|
+
Dialog,
|
|
323
|
+
{
|
|
324
|
+
open: importOpen,
|
|
325
|
+
onClose: () => !isValidating && !isImporting && setImportOpen(false),
|
|
326
|
+
fullWidth: true,
|
|
327
|
+
maxWidth: "md",
|
|
328
|
+
children: [
|
|
329
|
+
/* @__PURE__ */ jsx(DialogTitle, { sx: { px: 3, pt: 3, pb: 1 }, children: messages.importTitle }),
|
|
330
|
+
/* @__PURE__ */ jsx(DialogContent, { sx: { px: 3, pb: 3, pt: 0.5 }, children: /* @__PURE__ */ jsxs(Stack, { spacing: 1.5, children: [
|
|
331
|
+
validationError ? /* @__PURE__ */ jsx(Alert, { severity: "error", children: validationError }) : null,
|
|
332
|
+
importSuccess ? /* @__PURE__ */ jsx(Alert, { severity: "success", children: importSuccess }) : null,
|
|
333
|
+
/* @__PURE__ */ jsxs(Stack, { direction: { xs: "column", sm: "row" }, sx: { pt: 1 }, spacing: 1.5, children: [
|
|
334
|
+
/* @__PURE__ */ jsxs(
|
|
335
|
+
Button,
|
|
336
|
+
{
|
|
337
|
+
variant: "outlined",
|
|
338
|
+
component: "label",
|
|
339
|
+
sx: { whiteSpace: "nowrap", flexShrink: 0, minWidth: 132 },
|
|
340
|
+
children: [
|
|
341
|
+
importFile ? importFile.name : messages.chooseFile,
|
|
342
|
+
/* @__PURE__ */ jsx(
|
|
343
|
+
"input",
|
|
344
|
+
{
|
|
345
|
+
hidden: true,
|
|
346
|
+
type: "file",
|
|
347
|
+
accept: ".xlsx,.csv,.json",
|
|
348
|
+
onChange: (event) => {
|
|
349
|
+
var _a, _b;
|
|
350
|
+
const file = (_b = (_a = event.target.files) == null ? void 0 : _a[0]) != null ? _b : null;
|
|
351
|
+
setImportFile(file);
|
|
352
|
+
resetImportValidation();
|
|
353
|
+
if (!file) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const nextFormat = inferFormatFromFile(file.name);
|
|
357
|
+
if (nextFormat !== null && meta.import_formats.includes(nextFormat)) {
|
|
358
|
+
setImportFormat(nextFormat);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
),
|
|
366
|
+
/* @__PURE__ */ jsxs(FormControl, { fullWidth: true, children: [
|
|
367
|
+
/* @__PURE__ */ jsx(InputLabel, { id: "import-format-label", children: messages.formatLabel }),
|
|
368
|
+
/* @__PURE__ */ jsx(
|
|
369
|
+
Select,
|
|
370
|
+
{
|
|
371
|
+
labelId: "import-format-label",
|
|
372
|
+
value: importFormat,
|
|
373
|
+
label: messages.formatLabel,
|
|
374
|
+
onChange: (event) => {
|
|
375
|
+
resetImportValidation();
|
|
376
|
+
setImportFormat(event.target.value);
|
|
377
|
+
},
|
|
378
|
+
children: meta.import_formats.map((format) => /* @__PURE__ */ jsx(MenuItem, { value: format, children: format.toUpperCase() }, format))
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
] })
|
|
382
|
+
] }),
|
|
383
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(FormControl, { fullWidth: true, sx: { mt: 0.5 }, children: [
|
|
384
|
+
/* @__PURE__ */ jsx(InputLabel, { id: "import-conflict-mode-label", children: messages.conflictModeLabel }),
|
|
385
|
+
/* @__PURE__ */ jsx(
|
|
386
|
+
Select,
|
|
387
|
+
{
|
|
388
|
+
labelId: "import-conflict-mode-label",
|
|
389
|
+
value: conflictMode,
|
|
390
|
+
label: messages.conflictModeLabel,
|
|
391
|
+
onChange: (event) => {
|
|
392
|
+
resetImportValidation();
|
|
393
|
+
setConflictMode(event.target.value);
|
|
394
|
+
},
|
|
395
|
+
children: meta.available_conflict_modes.map((mode) => /* @__PURE__ */ jsx(MenuItem, { value: mode, children: messages.conflictMode(mode) }, mode))
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
] }) }),
|
|
399
|
+
/* @__PURE__ */ jsx(
|
|
400
|
+
FieldsSelector,
|
|
401
|
+
{
|
|
402
|
+
title: messages.importFieldsLabel,
|
|
403
|
+
fields: meta.import_fields,
|
|
404
|
+
selectedFields: selectedImportFields,
|
|
405
|
+
onChange: (value) => {
|
|
406
|
+
resetImportValidation();
|
|
407
|
+
setSelectedImportFields(value);
|
|
408
|
+
},
|
|
409
|
+
allLabel: messages.selectAllFields,
|
|
410
|
+
noneLabel: messages.clearFields
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
|
|
414
|
+
/* @__PURE__ */ jsx(
|
|
415
|
+
Button,
|
|
416
|
+
{
|
|
417
|
+
variant: "outlined",
|
|
418
|
+
onClick: () => void handleValidateImport({
|
|
419
|
+
client,
|
|
420
|
+
slug: context.slug,
|
|
421
|
+
file: importFile,
|
|
422
|
+
format: importFormat,
|
|
423
|
+
fields: selectedImportFields,
|
|
424
|
+
conflictMode,
|
|
425
|
+
setIsValidating,
|
|
426
|
+
setValidationResult,
|
|
427
|
+
setValidationError
|
|
428
|
+
}),
|
|
429
|
+
disabled: !importFile || selectedImportFields.length === 0 || isValidating || isImporting,
|
|
430
|
+
children: messages.validate
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
isValidating ? /* @__PURE__ */ jsx(CircularProgress, { size: 20 }) : null
|
|
434
|
+
] }),
|
|
435
|
+
validationResult ? /* @__PURE__ */ jsx(ValidationSummary, { messages, result: validationResult }) : null
|
|
436
|
+
] }) }),
|
|
437
|
+
/* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, pb: 3, pt: 0, gap: 1 }, children: [
|
|
438
|
+
/* @__PURE__ */ jsx(
|
|
439
|
+
Button,
|
|
440
|
+
{
|
|
441
|
+
onClick: () => setImportOpen(false),
|
|
442
|
+
disabled: isValidating || isImporting,
|
|
443
|
+
children: messages.cancel
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
/* @__PURE__ */ jsx(
|
|
447
|
+
Button,
|
|
448
|
+
{
|
|
449
|
+
variant: "contained",
|
|
450
|
+
onClick: () => void handleCommitImport({
|
|
451
|
+
client,
|
|
452
|
+
slug: context.slug,
|
|
453
|
+
file: importFile,
|
|
454
|
+
format: importFormat,
|
|
455
|
+
fields: selectedImportFields,
|
|
456
|
+
conflictMode,
|
|
457
|
+
setIsImporting,
|
|
458
|
+
setValidationError,
|
|
459
|
+
setImportSuccess,
|
|
460
|
+
refresh: context.refresh,
|
|
461
|
+
onSuccess: () => {
|
|
462
|
+
setValidationResult(null);
|
|
463
|
+
setImportFile(null);
|
|
464
|
+
},
|
|
465
|
+
messages
|
|
466
|
+
}),
|
|
467
|
+
disabled: !importFile || !validationResult || validationResult.summary.errors > 0 || isImporting,
|
|
468
|
+
children: isImporting ? messages.importing : messages.confirmImport
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
] })
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
] });
|
|
476
|
+
}
|
|
477
|
+
function FieldsSelector({
|
|
478
|
+
title,
|
|
479
|
+
fields,
|
|
480
|
+
selectedFields,
|
|
481
|
+
onChange,
|
|
482
|
+
allLabel,
|
|
483
|
+
noneLabel
|
|
484
|
+
}) {
|
|
485
|
+
return /* @__PURE__ */ jsxs(Stack, { children: [
|
|
486
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
|
|
487
|
+
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: title }),
|
|
488
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, children: [
|
|
489
|
+
/* @__PURE__ */ jsx(Button, { size: "small", onClick: () => onChange(fields.map((field) => field.name)), children: allLabel }),
|
|
490
|
+
/* @__PURE__ */ jsx(Button, { size: "small", onClick: () => onChange([]), children: noneLabel })
|
|
491
|
+
] })
|
|
492
|
+
] }),
|
|
493
|
+
/* @__PURE__ */ jsx(
|
|
494
|
+
List,
|
|
495
|
+
{
|
|
496
|
+
dense: true,
|
|
497
|
+
disablePadding: true,
|
|
498
|
+
sx: {
|
|
499
|
+
maxHeight: 280,
|
|
500
|
+
overflow: "auto"
|
|
501
|
+
},
|
|
502
|
+
children: fields.map((field) => /* @__PURE__ */ jsx(ListItem, { disableGutters: true, sx: { py: 0.125 }, children: /* @__PURE__ */ jsx(
|
|
503
|
+
FormControlLabel,
|
|
504
|
+
{
|
|
505
|
+
sx: { m: 0, alignItems: "center" },
|
|
506
|
+
control: /* @__PURE__ */ jsx(
|
|
507
|
+
Checkbox,
|
|
508
|
+
{
|
|
509
|
+
size: "small",
|
|
510
|
+
checked: selectedFields.includes(field.name),
|
|
511
|
+
onChange: (_, checked) => {
|
|
512
|
+
if (checked) {
|
|
513
|
+
onChange([...selectedFields, field.name]);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
onChange(selectedFields.filter((item) => item !== field.name));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
),
|
|
520
|
+
label: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.75, alignItems: "center", sx: { minHeight: 24 }, children: [
|
|
521
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { lineHeight: 1.35 }, children: field.label }),
|
|
522
|
+
field.label !== field.name ? /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { lineHeight: 1.2 }, children: field.name }) : null
|
|
523
|
+
] })
|
|
524
|
+
}
|
|
525
|
+
) }, field.name))
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
] });
|
|
529
|
+
}
|
|
530
|
+
function ValidationSummary({
|
|
531
|
+
messages,
|
|
532
|
+
result
|
|
533
|
+
}) {
|
|
534
|
+
return /* @__PURE__ */ jsxs(Stack, { spacing: 1.5, children: [
|
|
535
|
+
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: messages.validationSummaryTitle }),
|
|
536
|
+
/* @__PURE__ */ jsx(Typography, { color: "text.secondary", children: messages.validationSummary(result) }),
|
|
537
|
+
result.errors.length > 0 ? /* @__PURE__ */ jsx(Alert, { severity: "warning", children: /* @__PURE__ */ jsx(Stack, { spacing: 0.5, children: result.errors.slice(0, 10).map((error) => /* @__PURE__ */ jsxs(Typography, { variant: "body2", children: [
|
|
538
|
+
"#",
|
|
539
|
+
error.row_number,
|
|
540
|
+
": ",
|
|
541
|
+
error.message
|
|
542
|
+
] }, `${error.row_number}-${error.message}`)) }) }) : null
|
|
543
|
+
] });
|
|
544
|
+
}
|
|
545
|
+
function getDefaultSelectedFields(fields) {
|
|
546
|
+
const defaultSelected = fields.filter((field) => field.default_selected).map((field) => field.name);
|
|
547
|
+
if (defaultSelected.length > 0) {
|
|
548
|
+
return defaultSelected;
|
|
549
|
+
}
|
|
550
|
+
return fields.map((field) => field.name);
|
|
551
|
+
}
|
|
552
|
+
async function handleExport({
|
|
553
|
+
client,
|
|
554
|
+
context,
|
|
555
|
+
format,
|
|
556
|
+
fields,
|
|
557
|
+
setIsExporting,
|
|
558
|
+
onDone
|
|
559
|
+
}) {
|
|
560
|
+
setIsExporting(true);
|
|
561
|
+
try {
|
|
562
|
+
const response = await client.downloadExport(context.slug, {
|
|
563
|
+
format,
|
|
564
|
+
fields,
|
|
565
|
+
ids: context.isAllMatchingSelected ? [] : context.selectedIds,
|
|
566
|
+
select_all: context.isAllMatchingSelected || context.selectedIds.length === 0,
|
|
567
|
+
selection_scope: {
|
|
568
|
+
q: context.appliedQuery || void 0,
|
|
569
|
+
sort: context.sortValue || void 0,
|
|
570
|
+
filters: context.appliedFilters
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
const objectUrl = URL.createObjectURL(response.blob);
|
|
574
|
+
const link = document.createElement("a");
|
|
575
|
+
link.href = objectUrl;
|
|
576
|
+
link.download = response.filename;
|
|
577
|
+
document.body.appendChild(link);
|
|
578
|
+
link.click();
|
|
579
|
+
link.remove();
|
|
580
|
+
URL.revokeObjectURL(objectUrl);
|
|
581
|
+
onDone();
|
|
582
|
+
} finally {
|
|
583
|
+
setIsExporting(false);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async function handleValidateImport({
|
|
587
|
+
client,
|
|
588
|
+
slug,
|
|
589
|
+
file,
|
|
590
|
+
format,
|
|
591
|
+
fields,
|
|
592
|
+
conflictMode,
|
|
593
|
+
setIsValidating,
|
|
594
|
+
setValidationResult,
|
|
595
|
+
setValidationError
|
|
596
|
+
}) {
|
|
597
|
+
if (!file) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
setIsValidating(true);
|
|
601
|
+
setValidationError(null);
|
|
602
|
+
try {
|
|
603
|
+
const result = await client.validateImport(slug, {
|
|
604
|
+
file,
|
|
605
|
+
format,
|
|
606
|
+
fields,
|
|
607
|
+
conflict_mode: conflictMode
|
|
608
|
+
});
|
|
609
|
+
setValidationResult(result);
|
|
610
|
+
} catch (reason) {
|
|
611
|
+
setValidationResult(null);
|
|
612
|
+
setValidationError(reason instanceof Error ? reason.message : "Validation failed.");
|
|
613
|
+
} finally {
|
|
614
|
+
setIsValidating(false);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function handleCommitImport({
|
|
618
|
+
client,
|
|
619
|
+
slug,
|
|
620
|
+
file,
|
|
621
|
+
format,
|
|
622
|
+
fields,
|
|
623
|
+
conflictMode,
|
|
624
|
+
setIsImporting,
|
|
625
|
+
setValidationError,
|
|
626
|
+
setImportSuccess,
|
|
627
|
+
refresh,
|
|
628
|
+
onSuccess,
|
|
629
|
+
messages
|
|
630
|
+
}) {
|
|
631
|
+
if (!file) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
setIsImporting(true);
|
|
635
|
+
setValidationError(null);
|
|
636
|
+
try {
|
|
637
|
+
const result = await client.commitImport(slug, {
|
|
638
|
+
file,
|
|
639
|
+
format,
|
|
640
|
+
fields,
|
|
641
|
+
conflict_mode: conflictMode
|
|
642
|
+
});
|
|
643
|
+
await refresh();
|
|
644
|
+
onSuccess();
|
|
645
|
+
setImportSuccess(messages.importSuccess(result));
|
|
646
|
+
} catch (reason) {
|
|
647
|
+
setValidationError(reason instanceof Error ? reason.message : "Import failed.");
|
|
648
|
+
} finally {
|
|
649
|
+
setIsImporting(false);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function inferFormatFromFile(fileName) {
|
|
653
|
+
const normalized = fileName.toLowerCase();
|
|
654
|
+
if (normalized.endsWith(".xlsx")) {
|
|
655
|
+
return "xlsx";
|
|
656
|
+
}
|
|
657
|
+
if (normalized.endsWith(".csv")) {
|
|
658
|
+
return "csv";
|
|
659
|
+
}
|
|
660
|
+
if (normalized.endsWith(".json")) {
|
|
661
|
+
return "json";
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
function getMessages(locale) {
|
|
666
|
+
if (locale === "ru") {
|
|
667
|
+
return {
|
|
668
|
+
exportButton: "\u042D\u043A\u0441\u043F\u043E\u0440\u0442",
|
|
669
|
+
importButton: "\u0418\u043C\u043F\u043E\u0440\u0442",
|
|
670
|
+
exportTitle: "\u042D\u043A\u0441\u043F\u043E\u0440\u0442 \u0434\u0430\u043D\u043D\u044B\u0445",
|
|
671
|
+
importTitle: "\u0418\u043C\u043F\u043E\u0440\u0442 \u0434\u0430\u043D\u043D\u044B\u0445",
|
|
672
|
+
exporting: "\u042D\u043A\u0441\u043F\u043E\u0440\u0442...",
|
|
673
|
+
importing: "\u0418\u043C\u043F\u043E\u0440\u0442...",
|
|
674
|
+
exportConfirm: "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C",
|
|
675
|
+
confirmImport: "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0438\u043C\u043F\u043E\u0440\u0442",
|
|
676
|
+
cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
|
|
677
|
+
formatLabel: "\u0424\u043E\u0440\u043C\u0430\u0442",
|
|
678
|
+
exportFieldsLabel: "\u041F\u043E\u043B\u044F \u0434\u043B\u044F \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0430",
|
|
679
|
+
importFieldsLabel: "\u041F\u043E\u043B\u044F \u0434\u043B\u044F \u0438\u043C\u043F\u043E\u0440\u0442\u0430",
|
|
680
|
+
selectAllFields: "\u0412\u0441\u0435",
|
|
681
|
+
clearFields: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C",
|
|
682
|
+
chooseFile: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0444\u0430\u0439\u043B",
|
|
683
|
+
validate: "\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C",
|
|
684
|
+
conflictModeLabel: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043F\u0440\u0438 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0438 PK",
|
|
685
|
+
validationSummaryTitle: "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
|
|
686
|
+
exportCount: (count, selected) => selected ? `\u0411\u0443\u0434\u0435\u0442 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u0432: ${count}` : `\u0411\u0443\u0434\u0443\u0442 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0432\u0441\u0435 \u043E\u0431\u044A\u0435\u043A\u0442\u044B \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430: ${count}`,
|
|
687
|
+
conflictMode: (mode) => ({
|
|
688
|
+
auto_generate_pk: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 PK",
|
|
689
|
+
update_existing: "\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435",
|
|
690
|
+
skip_existing: "\u041F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435"
|
|
691
|
+
})[mode],
|
|
692
|
+
validationSummary: (result) => `\u0412\u0441\u0435\u0433\u043E \u0441\u0442\u0440\u043E\u043A: ${result.summary.total_rows}. \u0421\u043E\u0437\u0434\u0430\u0442\u044C: ${result.summary.create}. \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C: ${result.summary.update}. \u041F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C: ${result.summary.skip}. \u041E\u0448\u0438\u0431\u043E\u043A: ${result.summary.errors}.`,
|
|
693
|
+
importSuccess: (result) => `\u0418\u043C\u043F\u043E\u0440\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D. \u0421\u043E\u0437\u0434\u0430\u043D\u043E: ${result.created}, \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${result.updated}, \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E: ${result.skipped}.`
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
exportButton: "Export",
|
|
698
|
+
importButton: "Import",
|
|
699
|
+
exportTitle: "Export data",
|
|
700
|
+
importTitle: "Import data",
|
|
701
|
+
exporting: "Exporting...",
|
|
702
|
+
importing: "Importing...",
|
|
703
|
+
exportConfirm: "Export",
|
|
704
|
+
confirmImport: "Confirm import",
|
|
705
|
+
cancel: "Cancel",
|
|
706
|
+
formatLabel: "Format",
|
|
707
|
+
exportFieldsLabel: "Fields to export",
|
|
708
|
+
importFieldsLabel: "Fields to import",
|
|
709
|
+
selectAllFields: "All",
|
|
710
|
+
clearFields: "Clear",
|
|
711
|
+
chooseFile: "Choose file",
|
|
712
|
+
validate: "Validate",
|
|
713
|
+
conflictModeLabel: "Conflict mode",
|
|
714
|
+
validationSummaryTitle: "Validation summary",
|
|
715
|
+
exportCount: (count, selected) => selected ? `Objects to export: ${count}` : `All objects from the current result will be exported: ${count}`,
|
|
716
|
+
conflictMode: (mode) => ({
|
|
717
|
+
auto_generate_pk: "Auto-generate PK",
|
|
718
|
+
update_existing: "Update existing",
|
|
719
|
+
skip_existing: "Skip existing"
|
|
720
|
+
})[mode],
|
|
721
|
+
validationSummary: (result) => `Rows: ${result.summary.total_rows}. Create: ${result.summary.create}. Update: ${result.summary.update}. Skip: ${result.summary.skip}. Errors: ${result.summary.errors}.`,
|
|
722
|
+
importSuccess: (result) => `Import completed. Created: ${result.created}, updated: ${result.updated}, skipped: ${result.skipped}.`
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
export {
|
|
726
|
+
ModelImportExportActions,
|
|
727
|
+
createAxiosXLAdminImportExportClient,
|
|
728
|
+
createFetchXLAdminImportExportClient
|
|
729
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xladmin-import-export",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Optional import/export extension for xladmin.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/Artasov/xladmin.git",
|
|
13
|
+
"directory": "xladmin-frontend/packages/xladmin-import-export"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/Artasov/xladmin/tree/main/xladmin-frontend/packages/xladmin-import-export",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/Artasov/xladmin/issues"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "node ../../scripts/clean.mjs && tsup src/index.ts --format esm --dts",
|
|
35
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
36
|
+
"test": "vitest run --environment jsdom --pool threads"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@emotion/react": ">=11.14.0 <12.0.0",
|
|
40
|
+
"@emotion/styled": ">=11.14.0 <12.0.0",
|
|
41
|
+
"@mui/icons-material": ">=7.0.0 <8.0.0",
|
|
42
|
+
"@mui/material": ">=7.0.0 <8.0.0",
|
|
43
|
+
"axios": ">=1.0.0 <2.0.0",
|
|
44
|
+
"react": ">=19.0.0 <20.0.0",
|
|
45
|
+
"react-dom": ">=19.0.0 <20.0.0",
|
|
46
|
+
"xladmin": ">=0.2.8 <1.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"axios": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|