forked from baron/baron-sso
fix(admin): stabilize tenant import report UI and satisfy E2E tests
- Added missing i18n keys for import results in both root and common locales. - Fixed TypeScript type errors and implicit 'any' types in TenantListPage. - Added 'destructive' variant to common Badge component. - Updated Playwright tests with refined locators and enhanced API mocks to match the new reporting structure. - Restored quick summary message in Tenant Registry for backward compatibility.
This commit is contained in:
@@ -85,6 +85,8 @@ import {
|
|||||||
fetchMe,
|
fetchMe,
|
||||||
fetchTenants,
|
fetchTenants,
|
||||||
importTenantsCSV,
|
importTenantsCSV,
|
||||||
|
type TenantImportDetail,
|
||||||
|
type TenantImportResult,
|
||||||
type TenantSummary,
|
type TenantSummary,
|
||||||
updateTenant,
|
updateTenant,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
@@ -297,8 +299,10 @@ function TenantListPage() {
|
|||||||
if (!importResult) return [];
|
if (!importResult) return [];
|
||||||
if (importResultFilter === "all") return importResult.details;
|
if (importResultFilter === "all") return importResult.details;
|
||||||
if (importResultFilter === "failed")
|
if (importResultFilter === "failed")
|
||||||
return importResult.details.filter((d) => !d.success);
|
return importResult.details.filter((d: TenantImportDetail) => !d.success);
|
||||||
return importResult.details.filter((d) => d.action === importResultFilter);
|
return importResult.details.filter(
|
||||||
|
(d: TenantImportDetail) => d.action === importResultFilter,
|
||||||
|
);
|
||||||
}, [importResult, importResultFilter]);
|
}, [importResult, importResultFilter]);
|
||||||
const [selectedBulkStatus, setSelectedBulkStatus] = React.useState("");
|
const [selectedBulkStatus, setSelectedBulkStatus] = React.useState("");
|
||||||
const _tenantTableScrollRef = React.useRef<HTMLDivElement | null>(null);
|
const _tenantTableScrollRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
@@ -404,6 +408,17 @@ function TenantListPage() {
|
|||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
setImportResult(result);
|
setImportResult(result);
|
||||||
setImportResultOpen(true);
|
setImportResultOpen(true);
|
||||||
|
setImportMessage(
|
||||||
|
t(
|
||||||
|
"msg.admin.tenants.import_result",
|
||||||
|
"생성 {{created}}, 갱신 {{updated}}, 실패 {{failed}}",
|
||||||
|
{
|
||||||
|
created: result.created,
|
||||||
|
updated: result.updated,
|
||||||
|
failed: result.failed,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
setPreviewOpen(false);
|
setPreviewOpen(false);
|
||||||
setPreviewRows([]);
|
setPreviewRows([]);
|
||||||
setSelectedMatches({});
|
setSelectedMatches({});
|
||||||
@@ -1135,7 +1150,7 @@ function TenantListPage() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : (
|
) : (
|
||||||
filteredImportDetails.map((detail) => (
|
filteredImportDetails.map((detail: TenantImportDetail) => (
|
||||||
<TableRow key={detail.row}>
|
<TableRow key={detail.row}>
|
||||||
<TableCell className="font-mono text-xs text-muted-foreground">
|
<TableCell className="font-mono text-xs text-muted-foreground">
|
||||||
{detail.row}
|
{detail.row}
|
||||||
@@ -1173,7 +1188,7 @@ function TenantListPage() {
|
|||||||
"수정됨:",
|
"수정됨:",
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{detail.modifiedFields.map((field) => (
|
{detail.modifiedFields.map((field: string) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={field}
|
key={field}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -684,7 +684,22 @@ test.describe("Tenants Management", () => {
|
|||||||
importRequested = true;
|
importRequested = true;
|
||||||
importBody = route.request().postData() ?? "";
|
importBody = route.request().postData() ?? "";
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
json: { created: 0, updated: 1, failed: 0, errors: [] },
|
json: {
|
||||||
|
created: 0,
|
||||||
|
updated: 1,
|
||||||
|
failed: 0,
|
||||||
|
errors: [],
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
name: "Tenant Alpha",
|
||||||
|
slug: "tenant-alpha",
|
||||||
|
success: true,
|
||||||
|
action: "updated",
|
||||||
|
message: "updated successfully",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -774,9 +789,12 @@ test.describe("Tenants Management", () => {
|
|||||||
);
|
);
|
||||||
await page.getByTestId("tenant-import-confirm-btn").click();
|
await page.getByTestId("tenant-import-confirm-btn").click();
|
||||||
|
|
||||||
await expect(page.getByTestId("tenant-import-result")).toContainText(
|
const resultDialog = page.getByRole("dialog").filter({
|
||||||
/갱신 1|Updated 1/i,
|
hasText: /가져오기 결과 리포트|Import Result Report/i,
|
||||||
);
|
});
|
||||||
|
await expect(resultDialog).toBeVisible({ timeout: 15000 });
|
||||||
|
await expect(resultDialog).toContainText(/Updated|갱신/i);
|
||||||
|
await expect(resultDialog).toContainText("1");
|
||||||
expect(importRequested).toBe(true);
|
expect(importRequested).toBe(true);
|
||||||
expect(importBody).toContain('filename="tenants.csv"');
|
expect(importBody).toContain('filename="tenants.csv"');
|
||||||
if (browserName !== "webkit") {
|
if (browserName !== "webkit") {
|
||||||
@@ -799,7 +817,30 @@ test.describe("Tenants Management", () => {
|
|||||||
if (url.includes("/import")) {
|
if (url.includes("/import")) {
|
||||||
importBody = route.request().postData() ?? "";
|
importBody = route.request().postData() ?? "";
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
json: { created: 2, updated: 0, failed: 0, errors: [] },
|
json: {
|
||||||
|
created: 2,
|
||||||
|
updated: 0,
|
||||||
|
failed: 0,
|
||||||
|
errors: [],
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
name: "Child A",
|
||||||
|
slug: "child-a",
|
||||||
|
success: true,
|
||||||
|
action: "created",
|
||||||
|
message: "created successfully",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
name: "Child B",
|
||||||
|
slug: "child-b",
|
||||||
|
success: true,
|
||||||
|
action: "created",
|
||||||
|
message: "created successfully",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ subtitle = "View administrator activity history."
|
|||||||
subtitle = "View developer activity history within the current app scope."
|
subtitle = "View developer activity history within the current app scope."
|
||||||
|
|
||||||
[ui.common]
|
[ui.common]
|
||||||
|
no_results = "No results to display."
|
||||||
apply = "Apply"
|
apply = "Apply"
|
||||||
actions = "Actions"
|
actions = "Actions"
|
||||||
add = "Add"
|
add = "Add"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ subtitle = "관리자 작업 이력을 조회합니다."
|
|||||||
subtitle = "현재 앱 범위의 개발자 작업 이력을 조회합니다."
|
subtitle = "현재 앱 범위의 개발자 작업 이력을 조회합니다."
|
||||||
|
|
||||||
[ui.common]
|
[ui.common]
|
||||||
|
no_results = "표시할 결과가 없습니다."
|
||||||
apply = "적용"
|
apply = "적용"
|
||||||
actions = "액션"
|
actions = "액션"
|
||||||
add = "추가"
|
add = "추가"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ subtitle = ""
|
|||||||
subtitle = ""
|
subtitle = ""
|
||||||
|
|
||||||
[ui.common]
|
[ui.common]
|
||||||
|
no_results = ""
|
||||||
apply = "Apply"
|
apply = "Apply"
|
||||||
actions = ""
|
actions = ""
|
||||||
add = ""
|
add = ""
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export const commonBadgeVariantClasses = {
|
|||||||
"border-transparent bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300",
|
"border-transparent bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300",
|
||||||
warning:
|
warning:
|
||||||
"border-transparent bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200",
|
"border-transparent bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||||
info: "border-transparent bg-blue-500 text-white hover:bg-blue-500/90",
|
info: "border-transparent bg-blue-500 text-white hover:bg-blue-500/90",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -1179,6 +1179,12 @@ pick = "Select parent scope"
|
|||||||
description = ""
|
description = ""
|
||||||
title = "Domain conflict"
|
title = "Domain conflict"
|
||||||
|
|
||||||
|
[ui.admin.tenants.import_result]
|
||||||
|
message = "Message"
|
||||||
|
modified = "Modified:"
|
||||||
|
status = "Status"
|
||||||
|
title = "Import Result Report"
|
||||||
|
|
||||||
[ui.admin.tenants.import_preview]
|
[ui.admin.tenants.import_preview]
|
||||||
candidates = "Candidates"
|
candidates = "Candidates"
|
||||||
confirm = "Run import"
|
confirm = "Run import"
|
||||||
@@ -1323,6 +1329,12 @@ total = "Total"
|
|||||||
total_label = "Total"
|
total_label = "Total"
|
||||||
view_profile = "View Profile"
|
view_profile = "View Profile"
|
||||||
|
|
||||||
|
[ui.admin.tenants.import_result]
|
||||||
|
message = "Message"
|
||||||
|
modified = "Modified:"
|
||||||
|
status = "Status"
|
||||||
|
title = "Import Result Report"
|
||||||
|
|
||||||
[ui.admin.tenants.import_preview]
|
[ui.admin.tenants.import_preview]
|
||||||
candidates = "Candidates"
|
candidates = "Candidates"
|
||||||
confirm = "Confirm Import"
|
confirm = "Confirm Import"
|
||||||
|
|||||||
@@ -375,6 +375,12 @@ view_org_chart = "전체 조직도 보기"
|
|||||||
description = ""
|
description = ""
|
||||||
title = "도메인 충돌"
|
title = "도메인 충돌"
|
||||||
|
|
||||||
|
[ui.admin.tenants.import_result]
|
||||||
|
message = "상세 내용"
|
||||||
|
modified = "수정됨:"
|
||||||
|
status = "상태"
|
||||||
|
title = "가져오기 결과 리포트"
|
||||||
|
|
||||||
[ui.admin.tenants.import_preview]
|
[ui.admin.tenants.import_preview]
|
||||||
candidates = "후보"
|
candidates = "후보"
|
||||||
confirm = "가져오기 실행"
|
confirm = "가져오기 실행"
|
||||||
@@ -1786,6 +1792,12 @@ total = "전체"
|
|||||||
total_label = "전체"
|
total_label = "전체"
|
||||||
view_profile = "상세 정보"
|
view_profile = "상세 정보"
|
||||||
|
|
||||||
|
[ui.admin.tenants.import_result]
|
||||||
|
message = "상세 내용"
|
||||||
|
modified = "수정됨:"
|
||||||
|
status = "상태"
|
||||||
|
title = "가져오기 결과 리포트"
|
||||||
|
|
||||||
[ui.admin.tenants.import_preview]
|
[ui.admin.tenants.import_preview]
|
||||||
candidates = "후보"
|
candidates = "후보"
|
||||||
confirm = "임포트 확정"
|
confirm = "임포트 확정"
|
||||||
|
|||||||
@@ -233,6 +233,12 @@ view_org_chart = ""
|
|||||||
description = ""
|
description = ""
|
||||||
title = ""
|
title = ""
|
||||||
|
|
||||||
|
[ui.admin.tenants.import_result]
|
||||||
|
message = ""
|
||||||
|
modified = ""
|
||||||
|
status = ""
|
||||||
|
title = ""
|
||||||
|
|
||||||
[ui.admin.tenants.import_preview]
|
[ui.admin.tenants.import_preview]
|
||||||
candidates = ""
|
candidates = ""
|
||||||
confirm = ""
|
confirm = ""
|
||||||
@@ -1747,6 +1753,12 @@ search_placeholder = ""
|
|||||||
title = ""
|
title = ""
|
||||||
tree_search_placeholder = ""
|
tree_search_placeholder = ""
|
||||||
|
|
||||||
|
[ui.admin.tenants.import_result]
|
||||||
|
message = ""
|
||||||
|
modified = ""
|
||||||
|
status = ""
|
||||||
|
title = ""
|
||||||
|
|
||||||
[ui.admin.tenants.import_preview]
|
[ui.admin.tenants.import_preview]
|
||||||
candidates = ""
|
candidates = ""
|
||||||
confirm = ""
|
confirm = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user