diff --git a/Makefile b/Makefile index f7d76a82..941b14b2 100644 --- a/Makefile +++ b/Makefile @@ -276,18 +276,18 @@ code-check-front-lint: @echo "==> adminfront biome lint/format check" rm -rf adminfront/playwright-report adminfront/test-results cd adminfront && CI=true npx pnpm install --frozen-lockfile --ignore-scripts - cd adminfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false - cd adminfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false + cd adminfront && npx biome lint . + cd adminfront && npx biome format . @echo "==> devfront biome lint/format check" rm -rf devfront/playwright-report devfront/test-results cd devfront && npm ci --ignore-scripts - cd devfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false - cd devfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false + cd devfront && npx biome lint . + cd devfront && npx biome format . @echo "==> orgfront biome lint/format check" rm -rf orgfront/playwright-report orgfront/test-results cd orgfront && npm ci --ignore-scripts - cd orgfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false - cd orgfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false + cd orgfront && npx biome lint . + cd orgfront && npx biome format . code-check-backend-tests: @echo "==> backend tests" diff --git a/adminfront/scripts/serve-prod.mjs b/adminfront/scripts/serve-prod.mjs index a69d7bdb..ec25704d 100644 --- a/adminfront/scripts/serve-prod.mjs +++ b/adminfront/scripts/serve-prod.mjs @@ -3,7 +3,7 @@ import { createServer } from "node:http"; import { extname, join, normalize, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -const rootDir = fileURLToPath(new URL("..", import.meta.url)); +const _rootDir = fileURLToPath(new URL("..", import.meta.url)); const distDir = resolve( process.env.ADMINFRONT_BUILD_OUT_DIR ?? "/tmp/baron-sso-adminfront-dist", ); diff --git a/adminfront/src/app/routes.tsx b/adminfront/src/app/routes.tsx index 5e231dbc..47029154 100644 --- a/adminfront/src/app/routes.tsx +++ b/adminfront/src/app/routes.tsx @@ -19,7 +19,6 @@ import { TenantProfilePage } from "../features/tenants/routes/TenantProfilePage" import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage"; import { TenantWorksmobilePage } from "../features/tenants/routes/TenantWorksmobilePage"; import TenantUserGroupsTab from "../features/user-groups/routes/TenantUserGroupsTab"; -import { UserGroupDetailPage } from "../features/user-groups/routes/UserGroupDetailPage"; import UserCreatePage from "../features/users/UserCreatePage"; import UserDetailPage from "../features/users/UserDetailPage"; import UserListPage from "../features/users/UserListPage"; diff --git a/adminfront/src/features/audit/AuditLogsPage.tsx b/adminfront/src/features/audit/AuditLogsPage.tsx index d55540ba..128e7c6e 100644 --- a/adminfront/src/features/audit/AuditLogsPage.tsx +++ b/adminfront/src/features/audit/AuditLogsPage.tsx @@ -3,7 +3,6 @@ import type { AxiosError } from "axios"; import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react"; import * as React from "react"; import { - formatAuditValue, parseAuditDetails, resolveAuditAction, resolveAuditActor, diff --git a/adminfront/src/features/overview/GlobalOverviewPage.tsx b/adminfront/src/features/overview/GlobalOverviewPage.tsx index 90c12801..2b9095ef 100644 --- a/adminfront/src/features/overview/GlobalOverviewPage.tsx +++ b/adminfront/src/features/overview/GlobalOverviewPage.tsx @@ -23,7 +23,6 @@ import { fetchDataIntegrityReport, type RPUsageDailyMetric, type RPUsagePeriod, - type TenantSummary, } from "../../lib/adminApi"; import { t } from "../../lib/i18n"; diff --git a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx index 6dc881a9..fc78435d 100644 --- a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx @@ -5,7 +5,6 @@ import { Plus, Search, ShieldCheck, - Trash2, UserPlus, Users, } from "lucide-react"; @@ -28,7 +27,6 @@ import { DialogDescription, DialogHeader, DialogTitle, - DialogTrigger, } from "../../../components/ui/dialog"; import { Input } from "../../../components/ui/input"; import { @@ -68,7 +66,7 @@ function mergePendingMembers( export function TenantAdminsAndOwnersTab() { const auth = useAuth(); const navigate = useNavigate(); - const currentUserId = auth.user?.profile.sub; + const _currentUserId = auth.user?.profile.sub; const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>(); const tenantId = tenantIdParam ?? ""; const queryClient = useQueryClient(); @@ -187,7 +185,7 @@ export function TenantAdminsAndOwnersTab() { ), ); }, - onError: (err: AxiosError<{ error?: string }>, userId, context) => { + onError: (err: AxiosError<{ error?: string }>, _userId, context) => { if (context?.previousOwners) { queryClient.setQueryData( ["tenant-owners", tenantId], @@ -288,7 +286,7 @@ export function TenantAdminsAndOwnersTab() { t("msg.admin.tenants.admins.remove_success", "권한이 회수되었습니다."), ); }, - onError: (err: AxiosError<{ error?: string }>, userId, context) => { + onError: (err: AxiosError<{ error?: string }>, _userId, context) => { if (context?.previousAdmins) { queryClient.setQueryData( ["tenant-admins", tenantId], @@ -310,7 +308,7 @@ export function TenantAdminsAndOwnersTab() { } }; - const handleRemoveOwner = (userId: string, userName: string) => { + const _handleRemoveOwner = (userId: string, userName: string) => { if ( window.confirm( t( @@ -324,7 +322,7 @@ export function TenantAdminsAndOwnersTab() { } }; - const handleRemoveAdmin = (userId: string, userName: string) => { + const _handleRemoveAdmin = (userId: string, userName: string) => { if ( window.confirm( t( diff --git a/adminfront/src/features/tenants/routes/TenantDetailPage.tsx b/adminfront/src/features/tenants/routes/TenantDetailPage.tsx index fb452a3c..b7bae980 100644 --- a/adminfront/src/features/tenants/routes/TenantDetailPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantDetailPage.tsx @@ -1,7 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { Copy } from "lucide-react"; import { Link, Outlet, useLocation, useParams } from "react-router-dom"; -import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { fetchMe, fetchTenant } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; diff --git a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx index 87d226d2..df61e3b3 100644 --- a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx @@ -38,7 +38,6 @@ import { DialogFooter, DialogHeader, DialogTitle, - DialogTrigger, } from "../../../components/ui/dialog"; import { Input } from "../../../components/ui/input"; import { Label } from "../../../components/ui/label"; @@ -239,7 +238,7 @@ const UserGroupTreeNode: React.FC = ({ function TenantGroupsPage() { const params = useParams<{ tenantId: string }>(); const tenantId = params.tenantId ?? ""; - const queryClient = useQueryClient(); + const _queryClient = useQueryClient(); const [newGroupName, setNewGroupName] = useState(""); const [newGroupDesc, setNewGroupNameDesc] = useState(""); diff --git a/adminfront/src/features/tenants/routes/TenantListPage.tsx b/adminfront/src/features/tenants/routes/TenantListPage.tsx index 14a4b9c0..05f2b849 100644 --- a/adminfront/src/features/tenants/routes/TenantListPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantListPage.tsx @@ -4,7 +4,6 @@ import { useMutation, useQuery, } from "@tanstack/react-query"; -import { useVirtualizer } from "@tanstack/react-virtual"; import type { AxiosError } from "axios"; import { ArrowDown, @@ -14,7 +13,6 @@ import { ChevronDown, ChevronRight, Download, - ExternalLink, FileSpreadsheet, LayoutDashboard, List, @@ -28,22 +26,13 @@ import { import * as React from "react"; import { Link, useNavigate } from "react-router-dom"; import { PageHeader } from "../../../../../common/core/components/page"; -import { - SortableTableHead, - sortableTableHeadBaseClassName, - sortableTableHeaderClassName, -} from "../../../../../common/core/components/sort"; import { type SortConfig, type SortResolverMap, sortItems, toggleSort, } from "../../../../../common/core/utils"; -import { - commonStickyTableHeaderClass, - commonTableShellClass, - commonTableViewportClass, -} from "../../../../../common/ui/table"; +import { commonStickyTableHeaderClass } from "../../../../../common/ui/table"; import { RoleGuard } from "../../../components/auth/RoleGuard"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; @@ -71,7 +60,6 @@ import { DropdownMenuTrigger, } from "../../../components/ui/dropdown-menu"; import { Input } from "../../../components/ui/input"; -import { ScrollArea } from "../../../components/ui/scroll-area"; import { Select, SelectContent, @@ -79,7 +67,6 @@ import { SelectTrigger, SelectValue, } from "../../../components/ui/select"; -import { Separator } from "../../../components/ui/separator"; import { Switch } from "../../../components/ui/switch"; import { Table, @@ -132,10 +119,10 @@ import { const tenantCSVTemplate = "name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type\n"; const tenantPageSize = 500; -const tenantVirtualizationThreshold = 250; -const tenantEstimatedRowHeight = 73; -const tenantLoadAheadPx = 360; -const tenantLoadAheadRows = 30; +const _tenantVirtualizationThreshold = 250; +const _tenantEstimatedRowHeight = 73; +const _tenantLoadAheadPx = 360; +const _tenantLoadAheadRows = 30; type TenantSortKey = keyof TenantSummary | "recursiveMemberCount"; type TenantListRow = TenantSummary & { recursiveMemberCount: number }; @@ -301,7 +288,7 @@ function TenantListPage() { >({}); const [previewOpen, setPreviewOpen] = React.useState(false); const [selectedBulkStatus, setSelectedBulkStatus] = React.useState(""); - const tenantTableScrollRef = React.useRef(null); + const _tenantTableScrollRef = React.useRef(null); const { data: profile } = useQuery({ queryKey: ["me"], diff --git a/adminfront/src/features/tenants/routes/TenantSubTenantsPage.tsx b/adminfront/src/features/tenants/routes/TenantSubTenantsPage.tsx index 5fbd5edd..1fcb5111 100644 --- a/adminfront/src/features/tenants/routes/TenantSubTenantsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantSubTenantsPage.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { ArrowRight, Building2, Plus } from "lucide-react"; +import { Building2, Plus } from "lucide-react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { commonStickyTableHeaderClass, diff --git a/adminfront/src/features/tenants/routes/TenantUsersPage.tsx b/adminfront/src/features/tenants/routes/TenantUsersPage.tsx index 8c8638ce..a6d61460 100644 --- a/adminfront/src/features/tenants/routes/TenantUsersPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantUsersPage.tsx @@ -1,14 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; -import { - Loader2, - Mail, - MoreHorizontal, - Plus, - User, - UserMinus, - UserPlus, -} from "lucide-react"; +import { Loader2, Mail, Plus, User, UserPlus } from "lucide-react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { commonStickyTableHeaderClass } from "../../../../../common/ui/table"; import { Badge } from "../../../components/ui/badge"; @@ -19,12 +11,6 @@ import { CardHeader, CardTitle, } from "../../../components/ui/card"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "../../../components/ui/dropdown-menu"; import { Table, TableBody, @@ -80,7 +66,7 @@ function TenantUsersPage() { }, }); - const handleRemoveMember = (userId: string, userName: string) => { + const _handleRemoveMember = (userId: string, userName: string) => { if (!tenantSlug) return; if ( window.confirm( diff --git a/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx b/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx index a4533e21..5fb3b1f1 100644 --- a/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx +++ b/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx @@ -1,6 +1,5 @@ import { useQuery } from "@tanstack/react-query"; import { Building2, Plus, Users } from "lucide-react"; -import { useState } from "react"; import { Link } from "react-router-dom"; import { commonStickyTableHeaderClass } from "../../../../../common/ui/table"; import { Badge } from "../../../components/ui/badge"; diff --git a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx index b46d00ae..5f4312ba 100644 --- a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx +++ b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx @@ -6,7 +6,6 @@ import { Building2, ChevronDown, ChevronRight, - CornerDownRight, Download, ExternalLink, FolderOpen, @@ -41,7 +40,6 @@ import { DialogFooter, DialogHeader, DialogTitle, - DialogTrigger, } from "../../../components/ui/dialog"; import { DropdownMenu, @@ -52,7 +50,6 @@ import { DropdownMenuTrigger, } from "../../../components/ui/dropdown-menu"; import { Input } from "../../../components/ui/input"; -import { Label } from "../../../components/ui/label"; import { ScrollArea } from "../../../components/ui/scroll-area"; import { Table, @@ -62,15 +59,8 @@ import { TableHeader, TableRow, } from "../../../components/ui/table"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "../../../components/ui/tabs"; import { toast } from "../../../components/ui/use-toast"; import { - createUser, exportTenantsCSV, fetchAllTenants, fetchUsers, @@ -413,7 +403,7 @@ const MemberTable: React.FC<{ function TenantUserGroupsTab() { const { tenantId } = useParams<{ tenantId: string }>(); - const navigate = useNavigate(); + const _navigate = useNavigate(); const queryClient = useQueryClient(); const [selectedNodeId, setSelectedNodeId] = useState(tenantId || ""); @@ -855,7 +845,7 @@ const UserAddDialog: React.FC<{ try { const res = await fetchUsers(20, 0, userSearch); setSearchResults(res.items); - } catch (err) { + } catch (_err) { toast.error(t("msg.admin.users.list.fetch_error", "사용자 검색 실패")); } finally { setIsSearching(false); diff --git a/adminfront/src/features/users/UserCreatePage.tsx b/adminfront/src/features/users/UserCreatePage.tsx index d47a49c5..f7cccce0 100644 --- a/adminfront/src/features/users/UserCreatePage.tsx +++ b/adminfront/src/features/users/UserCreatePage.tsx @@ -8,7 +8,6 @@ import { Plus, Save, Trash2, - Mail, X, } from "lucide-react"; import * as React from "react"; @@ -22,7 +21,6 @@ import { CardHeader, CardTitle, } from "../../components/ui/card"; -import { Checkbox } from "../../components/ui/checkbox"; import { Dialog, DialogContent, @@ -184,7 +182,7 @@ function UserCreatePage() { if (e.key === "Enter" || e.key === "," || e.key === " ") { e.preventDefault(); const value = newSubEmail.trim().replace(/,/g, ""); - if (value && value.includes("@") && !currentSubEmails.includes(value)) { + if (value?.includes("@") && !currentSubEmails.includes(value)) { setValue("metadata.sub_email", [...currentSubEmails, value], { shouldDirty: true, }); @@ -667,8 +665,7 @@ function UserCreatePage() { onClick={() => { const value = newSubEmail.trim().replace(/,/g, ""); if ( - value && - value.includes("@") && + value?.includes("@") && !currentSubEmails.includes(value) ) { setValue( diff --git a/adminfront/src/features/users/UserDetailPage.tsx b/adminfront/src/features/users/UserDetailPage.tsx index d2857613..c5e3a6c5 100644 --- a/adminfront/src/features/users/UserDetailPage.tsx +++ b/adminfront/src/features/users/UserDetailPage.tsx @@ -35,7 +35,6 @@ import { CardHeader, CardTitle, } from "../../components/ui/card"; -import { Checkbox } from "../../components/ui/checkbox"; import { Dialog, DialogContent, @@ -95,7 +94,10 @@ import { resolvePersonalTenant } from "./utils/personalTenant"; type UserFormValues = Omit & { email: string; - metadata: Record | string[] | undefined> & { + metadata: Record< + string, + Record | string[] | undefined + > & { sub_email?: string[]; }; }; @@ -322,8 +324,8 @@ function UserDetailPage() { const userId = params.id ?? ""; const navigate = useNavigate(); const queryClient = useQueryClient(); - const [error, setError] = React.useState(null); - const [successMsg, setSuccessMsg] = React.useState(null); + const [_error, _setError] = React.useState(null); + const [_successMsg, _setSuccessMsg] = React.useState(null); const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false); const [generatedPassword, setGeneratedPassword] = React.useState< string | null @@ -419,7 +421,7 @@ function UserDetailPage() { if (e.key === "Enter" || e.key === "," || e.key === " ") { e.preventDefault(); const value = newSubEmail.trim().replace(/,/g, ""); - if (value && value.includes("@") && !currentSubEmails.includes(value)) { + if (value?.includes("@") && !currentSubEmails.includes(value)) { setValue("metadata.sub_email", [...currentSubEmails, value], { shouldDirty: true, }); @@ -595,7 +597,7 @@ function UserDetailPage() { ); }; - const setPrimaryAppointment = (targetIndex: number) => { + const _setPrimaryAppointment = (targetIndex: number) => { setAdditionalAppointments((current) => current.map((appointment, index) => ({ ...appointment, @@ -793,11 +795,13 @@ function UserDetailPage() { sub_email: rawSubEmail, ...safeMetadata } = cleanMetadata; - + // Parse sub_email let sub_email: string[] = []; if (Array.isArray(rawSubEmail)) { - sub_email = rawSubEmail.filter(e => typeof e === 'string' && e.includes("@")); + sub_email = rawSubEmail.filter( + (e) => typeof e === "string" && e.includes("@"), + ); } else if (typeof rawSubEmail === "string" && rawSubEmail.trim() !== "") { sub_email = rawSubEmail .split(/[;,\n\r\t]/) @@ -816,7 +820,7 @@ function UserDetailPage() { }; // email cannot be updated directly via this API in current backend implementation, // so we delete it from payload if it spread - // @ts-ignore + // @ts-expect-error delete payload.email; payload.role = undefined; @@ -992,16 +996,21 @@ function UserDetailPage() { {user.email} - {Boolean(user.metadata?.sub_email && - Array.isArray(user.metadata.sub_email) && - user.metadata.sub_email.length > 0) && ( -
- - - +{Array.isArray(user.metadata?.sub_email) ? user.metadata.sub_email.length : 0} - -
- )} + {Boolean( + user.metadata?.sub_email && + Array.isArray(user.metadata.sub_email) && + user.metadata.sub_email.length > 0, + ) && ( +
+ + + + + {Array.isArray(user.metadata?.sub_email) + ? user.metadata.sub_email.length + : 0} + +
+ )} {user.phone && (
@@ -1170,8 +1179,7 @@ function UserDetailPage() { onClick={() => { const value = newSubEmail.trim().replace(/,/g, ""); if ( - value && - value.includes("@") && + value?.includes("@") && !currentSubEmails.includes(value) ) { setValue( diff --git a/adminfront/src/features/users/UserListPage.render.test.tsx b/adminfront/src/features/users/UserListPage.render.test.tsx index 72c5145f..6694f252 100644 --- a/adminfront/src/features/users/UserListPage.render.test.tsx +++ b/adminfront/src/features/users/UserListPage.render.test.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { createI18nMock } from "../../test/i18nMock"; diff --git a/adminfront/src/features/users/UserListPage.tsx b/adminfront/src/features/users/UserListPage.tsx index fa5d4795..a914b63a 100644 --- a/adminfront/src/features/users/UserListPage.tsx +++ b/adminfront/src/features/users/UserListPage.tsx @@ -13,7 +13,6 @@ import { ChevronDown, ChevronLeft, ChevronRight, - Download, FileDown, FileSpreadsheet, LayoutDashboard, @@ -268,7 +267,7 @@ const UserListSearchControls = React.memo(function UserListSearchControls({ }); function UserListPage() { - const navigate = useNavigate(); + const _navigate = useNavigate(); const [page, setPage] = React.useState(1); const [search, setSearch] = React.useState(""); const [selectedCompany, setSelectedCompany] = React.useState(""); @@ -563,7 +562,7 @@ function UserListPage() { }, }); - const handleApplyBulkStatus = () => { + const _handleApplyBulkStatus = () => { if (selectedUserIds.length === 0 || !selectedBulkStatus) return; bulkUpdateMutation.mutate({ userIds: selectedUserIds, @@ -571,7 +570,7 @@ function UserListPage() { }); }; - const handleApplyBulkPermission = () => { + const _handleApplyBulkPermission = () => { if (selectedUserIds.length === 0 || !selectedBulkPermission) return; bulkUpdateMutation.mutate({ userIds: selectedUserIds, @@ -594,7 +593,7 @@ function UserListPage() { } }; - const handleDelete = (userId: string, userName: string) => { + const _handleDelete = (userId: string, userName: string) => { if ( !window.confirm( t( diff --git a/adminfront/src/features/users/components/UserBulkMoveGroupModal.tsx b/adminfront/src/features/users/components/UserBulkMoveGroupModal.tsx index 41232425..c045d2f9 100644 --- a/adminfront/src/features/users/components/UserBulkMoveGroupModal.tsx +++ b/adminfront/src/features/users/components/UserBulkMoveGroupModal.tsx @@ -19,8 +19,6 @@ import { bulkUpdateUsers, fetchAllTenants, fetchGroups, - type GroupSummary, - type TenantSummary, type UserSummary, } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; @@ -49,7 +47,7 @@ export function UserBulkMoveGroupModal({ const [searchTerm, setSearchTerm] = React.useState(""); const [acknowledgeWarning, setAcknowledgeWarning] = React.useState(false); - const queryClient = useQueryClient(); + const _queryClient = useQueryClient(); const { data: tenantsData } = useQuery({ queryKey: ["tenants", "all"], diff --git a/adminfront/src/features/users/orgChartPicker.ts b/adminfront/src/features/users/orgChartPicker.ts index 7d8cbf2d..20f06746 100644 --- a/adminfront/src/features/users/orgChartPicker.ts +++ b/adminfront/src/features/users/orgChartPicker.ts @@ -102,7 +102,7 @@ export function isHanmacFamilyTenant( tenants: T[], hanmacFamilyTenantId?: string, ) { - if (!tenant || !tenant.id) return false; + if (!tenant?.id) return false; const rootTenantId = resolveHanmacFamilyTenantId( tenants, diff --git a/adminfront/src/lib/tenantTree.ts b/adminfront/src/lib/tenantTree.ts index 1c6ae529..80606f19 100644 --- a/adminfront/src/lib/tenantTree.ts +++ b/adminfront/src/lib/tenantTree.ts @@ -24,7 +24,7 @@ export function buildTenantFullTree( }); } - const visitedDuringBuild = new Set(); + const _visitedDuringBuild = new Set(); // Build initial children relations and prevent simple cycles for (const t of allTenants) { if (t.parentId && t.parentId !== t.id) { diff --git a/adminfront/tests/tenants.spec.ts b/adminfront/tests/tenants.spec.ts index f12eeba7..77a9d2dd 100644 --- a/adminfront/tests/tenants.spec.ts +++ b/adminfront/tests/tenants.spec.ts @@ -258,7 +258,7 @@ test.describe("Tenants Management", () => { page, }) => { await page.setViewportSize({ width: 900, height: 700 }); - let requestCount = 0; + let _requestCount = 0; await page.route("**/api/v1/admin/tenants**", async (route) => { if (route.request().method() !== "GET") { @@ -266,7 +266,7 @@ test.describe("Tenants Management", () => { } const url = new URL(route.request().url()); const cursor = url.searchParams.get("cursor"); - requestCount += 1; + _requestCount += 1; if (!cursor) { return route.fulfill({ diff --git a/adminfront/tests/users_bulk_secondary.spec.ts b/adminfront/tests/users_bulk_secondary.spec.ts index d7a03e7b..79d51bdf 100644 --- a/adminfront/tests/users_bulk_secondary.spec.ts +++ b/adminfront/tests/users_bulk_secondary.spec.ts @@ -5,7 +5,9 @@ test.describe("Users Bulk Upload Secondary Emails", () => { await page.addInitScript(() => { window.localStorage.setItem("locale", "ko"); window.localStorage.setItem("admin_session", "fake-token"); - (window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })._IS_TEST_MODE = true; + ( + window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean } + )._IS_TEST_MODE = true; const authData = { access_token: "fake-token", @@ -13,12 +15,20 @@ test.describe("Users Bulk Upload Secondary Emails", () => { profile: { sub: "admin-user", name: "Admin", role: "super_admin" }, expires_at: Math.floor(Date.now() / 1000) + 36000, }; - window.localStorage.setItem("oidc.user:http://localhost:5000/oidc:adminfront", JSON.stringify(authData)); + window.localStorage.setItem( + "oidc.user:http://localhost:5000/oidc:adminfront", + JSON.stringify(authData), + ); }); await page.route("**/api/v1/user/me", async (route) => { return route.fulfill({ - json: { id: "admin-user", name: "Admin", role: "super_admin", manageableTenants: [] }, + json: { + id: "admin-user", + name: "Admin", + role: "super_admin", + manageableTenants: [], + }, headers: { "Access-Control-Allow-Origin": "*" }, }); }); @@ -31,7 +41,7 @@ test.describe("Users Bulk Upload Secondary Emails", () => { }); await page.route("**/api/v1/admin/users*", async (route) => { - if(route.request().url().includes("/bulk")) { + if (route.request().url().includes("/bulk")) { return route.continue(); } return route.fulfill({ @@ -45,14 +55,20 @@ test.describe("Users Bulk Upload Secondary Emails", () => { }); }); - test("should parse secondary_emails and send to backend", async ({ page }) => { + test("should parse secondary_emails and send to backend", async ({ + page, + }) => { let bulkPayload: any = null; await page.route("**/api/v1/admin/users/bulk", async (route) => { if (route.request().method() === "POST") { bulkPayload = route.request().postDataJSON(); return route.fulfill({ - json: { results: [{ email: "test@example.com", success: true, userId: "u-1" }] }, + json: { + results: [ + { email: "test@example.com", success: true, userId: "u-1" }, + ], + }, headers: { "Access-Control-Allow-Origin": "*" }, }); } @@ -60,21 +76,26 @@ test.describe("Users Bulk Upload Secondary Emails", () => { }); await page.goto("/users"); - await expect(page.getByTestId("page-title")).toContainText(/사용자|Users/i, { timeout: 20000 }); + await expect(page.getByTestId("page-title")).toContainText( + /사용자|Users/i, + { timeout: 20000 }, + ); await page.getByTestId("user-data-mgmt-btn").click(); - await page.getByRole("menuitem", { name: /일괄 임포트|일괄 등록|Bulk Import/i }).click(); + await page + .getByRole("menuitem", { name: /일괄 임포트|일괄 등록|Bulk Import/i }) + .click(); // Create a mock CSV with secondary_emails const csvContent = `email,sub_email,name,phone,role,tenant_slug\ntest@example.com,sub1@test.com;sub2@test.com,홍길동,010-1234-5678,user,tenant-slug`; - const fileChooserPromise = page.waitForEvent('filechooser'); + const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByText(/파일 선택|Change file|Select file/i).click(); const fileChooser = await fileChooserPromise; await fileChooser.setFiles({ - name: 'users_with_secondary.csv', - mimeType: 'text/csv', + name: "users_with_secondary.csv", + mimeType: "text/csv", buffer: Buffer.from(csvContent), }); @@ -87,9 +108,9 @@ test.describe("Users Bulk Upload Secondary Emails", () => { expect(bulkPayload).not.toBeNull(); expect(bulkPayload.users).toHaveLength(1); - + // The most important check - does it parse to the metadata expect(bulkPayload.users[0].metadata.sub_email).toContain("sub1@test.com"); expect(bulkPayload.users[0].metadata.sub_email).toContain("sub2@test.com"); }); -}); \ No newline at end of file +});