@goplusvn/core 0.1.86 → 0.1.87

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@goplusvn/core",
3
3
  "description": "GoPlusVN Platform Kit - ERP kernel: layout, RBAC, CRUD, multi-tenant, system pages",
4
- "version": "0.1.86",
4
+ "version": "0.1.87",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org",
@@ -94,12 +94,22 @@ export interface ImportDialogProps {
94
94
  apiUrl: string;
95
95
  /**
96
96
  * Nguồn file mẫu — chọn MỘT:
97
- * - `templateApiUrl`: GET mẫu do server dựng (nhiều sheet, chứng từ), hoặc
98
- * - `config`: dựng mẫu ngay ở client từ EntityConfig.
99
- * `config` thì dialog còn kiểm tra file TRƯỚC khi gửi gửi kèm `format`.
97
+ * - `templateApiUrl`: GET mẫu do server dựng (nhiều sheet, chứng từ),
98
+ * - `config`: dựng mẫu ngay ở client từ EntityConfig, hoặc
99
+ * - `onDownloadTemplate`: app tự dựng (mẫu phụ thuộc cấu hình đang chạy
100
+ * danh sách ca, danh sách món… — nên không khai trước bằng config được,
101
+ * mà app lại không tự resolve được `xlsx`; core có).
100
102
  */
101
103
  templateApiUrl?: string;
102
104
  config?: EntityConfig;
105
+ onDownloadTemplate?: () => void | Promise<void>;
106
+ /**
107
+ * Nội dung riêng của app chèn NGAY DƯỚI khối kết quả — chỗ đặt nút "Tải file
108
+ * lỗi". Nhận nguyên văn thân JSON server trả, vì mỗi nghiệp vụ đính kèm một
109
+ * kiểu chi tiết khác nhau (dòng xem trước, lý do từng dòng…) mà dialog không
110
+ * nên biết.
111
+ */
112
+ renderResultExtra?: (raw: unknown) => ReactNode;
103
113
  /** Không có quyền nhập thì không render gì. */
104
114
  canImport?: boolean;
105
115
  onSuccess?: () => void;
@@ -132,6 +142,8 @@ export function ImportDialog({
132
142
  apiUrl,
133
143
  templateApiUrl,
134
144
  config,
145
+ onDownloadTemplate,
146
+ renderResultExtra,
135
147
  canImport = true,
136
148
  onSuccess,
137
149
  tasksPath,
@@ -140,6 +152,7 @@ export function ImportDialog({
140
152
  const [file, setFile] = useState<File | null>(null);
141
153
  const [loading, setLoading] = useState(false);
142
154
  const [result, setResult] = useState<NormalizedResult | null>(null);
155
+ const [rawResult, setRawResult] = useState<unknown>(null);
143
156
  const [format, setFormat] = useState<ImportOptions["format"]>("xlsx");
144
157
  const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
145
158
 
@@ -164,6 +177,7 @@ export function ImportDialog({
164
177
  (selectedFile: File) => {
165
178
  setFile(selectedFile);
166
179
  setResult(null);
180
+ setRawResult(null);
167
181
  if (configMode) {
168
182
  const extension = selectedFile.name.split(".").pop()?.toLowerCase();
169
183
  if (extension === "json") setFormat("json");
@@ -177,6 +191,7 @@ export function ImportDialog({
177
191
  const clearSelectedFile = () => {
178
192
  setFile(null);
179
193
  setResult(null);
194
+ setRawResult(null);
180
195
  };
181
196
 
182
197
  const onDrop = useCallback(
@@ -216,6 +231,10 @@ export function ImportDialog({
216
231
 
217
232
  const handleDownloadTemplate = async () => {
218
233
  try {
234
+ if (onDownloadTemplate) {
235
+ await onDownloadTemplate();
236
+ return;
237
+ }
219
238
  if (config) {
220
239
  const blob = await generateXLSXTemplate(config);
221
240
  triggerBlobDownload(blob, `${config.name}_template.xlsx`);
@@ -242,6 +261,7 @@ export function ImportDialog({
242
261
 
243
262
  setLoading(true);
244
263
  setResult(null);
264
+ setRawResult(null);
245
265
 
246
266
  try {
247
267
  // Chế độ config kiểm tra ở client trước, để người dùng thấy lỗi cột mà
@@ -297,6 +317,7 @@ export function ImportDialog({
297
317
  setOpen(false);
298
318
  setFile(null);
299
319
  setResult(null);
320
+ setRawResult(null);
300
321
  setLoading(false);
301
322
  return;
302
323
  }
@@ -307,6 +328,7 @@ export function ImportDialog({
307
328
 
308
329
  const normalized = normalizeImportResult(raw);
309
330
  setResult(normalized);
331
+ setRawResult(raw);
310
332
 
311
333
  if (normalized.success && normalized.importedCount > 0) {
312
334
  if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current);
@@ -314,6 +336,7 @@ export function ImportDialog({
314
336
  setOpen(false);
315
337
  setFile(null);
316
338
  setResult(null);
339
+ setRawResult(null);
317
340
  onSuccess?.();
318
341
  successTimeoutRef.current = null;
319
342
  }, 2000);
@@ -353,6 +376,7 @@ export function ImportDialog({
353
376
  if (!open) {
354
377
  setFile(null);
355
378
  setResult(null);
379
+ setRawResult(null);
356
380
  }
357
381
  }, [open]);
358
382
 
@@ -554,13 +578,23 @@ export function ImportDialog({
554
578
  </AlertDescription>
555
579
  </Alert>
556
580
  )}
581
+
582
+ {result && renderResultExtra?.(rawResult)}
557
583
  </div>
558
584
 
559
585
  <DialogFooter className="flex flex-row sm:justify-end gap-2 space-x-0 pt-2">
560
- <Button variant="outline" className="flex-1 sm:flex-none" onClick={() => setOpen(false)}>
586
+ <Button
587
+ variant="outline"
588
+ className="flex-1 sm:flex-none"
589
+ onClick={() => setOpen(false)}
590
+ >
561
591
  Đóng
562
592
  </Button>
563
- <Button className="flex-1 sm:flex-none" onClick={handleImport} disabled={!file || loading}>
593
+ <Button
594
+ className="flex-1 sm:flex-none"
595
+ onClick={handleImport}
596
+ disabled={!file || loading}
597
+ >
564
598
  {loading ? "Đang import..." : "Import"}
565
599
  </Button>
566
600
  </DialogFooter>