diff --git a/.gitignore b/.gitignore index 1572d60a..191012ce 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ orgfront/dist/ orgfront/.vite/ .pnpm-store .playwright-mcp +node_modules diff --git a/adminfront/pnpm-workspace.yaml b/adminfront/pnpm-workspace.yaml deleted file mode 100644 index 59f9a7d3..00000000 --- a/adminfront/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -allowBuilds: - '@biomejs/biome': true - esbuild: false diff --git a/adminfront/src/features/audit/AuditLogsPage.tsx b/adminfront/src/features/audit/AuditLogsPage.tsx index 2d7f43e8..05c9542e 100644 --- a/adminfront/src/features/audit/AuditLogsPage.tsx +++ b/adminfront/src/features/audit/AuditLogsPage.tsx @@ -2,12 +2,6 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react"; import * as React from "react"; -import { - parseAuditDetails, - resolveAuditAction, - resolveAuditActor, -} from "../../../../common/core/audit"; -import { AuditLogTable } from "../../../../common/core/components/audit"; import { PageHeader } from "../../../../common/core/components/page"; import { SearchFilterBar } from "../../../../common/ui/search-filter-bar"; import { Badge } from "../../components/ui/badge"; @@ -23,6 +17,7 @@ import { Input } from "../../components/ui/input"; import type { AuditLog } from "../../lib/adminApi"; import { fetchAuditLogs } from "../../lib/adminApi"; import { t } from "../../lib/i18n"; +import { VirtualizedAuditLogTable } from "./VirtualizedAuditLogTable"; function AuditLogsPage() { const [searchActorId, setSearchActorId] = React.useState(""); @@ -40,8 +35,23 @@ function AuditLogsPage() { isFetching, refetch, } = useInfiniteQuery({ - queryKey: ["audit-logs"], - queryFn: ({ pageParam }) => fetchAuditLogs(50, pageParam), + queryKey: [ + "audit-logs", + deferredSearchActorId, + deferredSearchAction, + statusFilter, + ], + queryFn: ({ pageParam }) => { + const search = [deferredSearchActorId, deferredSearchAction] + .filter(Boolean) + .join(" "); + return fetchAuditLogs( + 50, + pageParam, + search || undefined, + statusFilter === "all" ? undefined : statusFilter, + ); + }, initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage) => lastPage.next_cursor || undefined, }); @@ -51,24 +61,6 @@ function AuditLogsPage() { (page) => page?.items?.filter((item): item is AuditLog => Boolean(item)) ?? [], ) ?? []; - const filteredLogs = React.useMemo( - () => - logs.filter((row) => { - const details = parseAuditDetails(row.details); - const actorLabel = resolveAuditActor(row, details).toLowerCase(); - const actionLabel = resolveAuditAction(row, details).toLowerCase(); - const matchesActor = - deferredSearchActorId === "" || - actorLabel.includes(deferredSearchActorId.toLowerCase()); - const matchesAction = - deferredSearchAction === "" || - actionLabel.includes(deferredSearchAction.toLowerCase()); - const matchesStatus = - statusFilter === "all" || row.status === statusFilter; - return matchesActor && matchesAction && matchesStatus; - }), - [logs, deferredSearchActorId, deferredSearchAction, statusFilter], - ); return (
@@ -83,7 +75,7 @@ function AuditLogsPage() { <> {t("msg.common.audit.registry.count", "총 {{count}}개 로그", { - count: filteredLogs.length, + count: logs.length, })} + ) : null} +
+ +
+
{actionLabel}
+
+
+
+ {targetLabel} + {targetLabel !== "-" ? ( + + ) : null} +
+
+
+ + {row.status} + +
+
+ +
+ + {expanded && ( +
+
+
+
+ {t("ui.common.audit.details.request", "Request")} +
+
+ {t( + "ui.common.audit.details.request_id", + "Request ID · {{value}}", + { value: formatAuditValue(details.request_id) }, + )} +
+
+ {t( + "ui.common.audit.details.event_id", + "Event ID · {{value}}", + { value: formatAuditValue(row.event_id) }, + )} +
+
+ {t("ui.common.audit.details.ip", "IP · {{value}}", { + value: formatAuditValue(row.ip_address), + })} +
+
+ {t("ui.common.audit.details.method", "Method · {{value}}", { + value: formatAuditValue(details.method), + })} +
+
+ {t("ui.common.audit.details.path", "Path · {{value}}", { + value: formatAuditValue(details.path), + })} +
+
+ {t( + "ui.common.audit.details.latency", + "Latency · {{value}}", + { + value: + details.latency_ms !== undefined + ? `${details.latency_ms}ms` + : "-", + }, + )} +
+
+
+
+ {t("ui.common.audit.details.actor", "Actor")} +
+
+ {t( + "ui.common.audit.details.actor_id", + "User ID · {{value}}", + { value: actorLabel }, + )} +
+
+ {t("ui.common.audit.details.tenant", "Tenant · {{value}}", { + value: formatAuditValue(details.tenant_id), + })} +
+
+ {t("ui.common.audit.details.device", "Device · {{value}}", { + value: formatAuditValue(row.device_id), + })} +
+
+ {t( + "ui.common.audit.details.target", + "Client ID · {{value}}", + { value: targetLabel }, + )} +
+
+
+
+ {t("ui.common.audit.details.result", "Result")} +
+
+ {t("ui.common.audit.details.error", "Error · {{value}}", { + value: formatAuditValue(details.error), + })} +
+
+ {t("ui.common.audit.details.before", "Before · {{value}}", { + value: formatAuditValue(details.before), + })} +
+
+ {t("ui.common.audit.details.after", "After · {{value}}", { + value: formatAuditValue(details.after), + })} +
+
+
+
+ )} + + + ); + }; + + return ( +
+
+
+ + + + + + + + + + + + {isTest + ? logs.map((row, index) => renderRow(row, index)) + : virtualRows.map((virtualRow) => + renderRow( + logs[virtualRow.index], + virtualRow.index, + virtualRow, + ), + )} + {logs.length === 0 && !loading && ( + + + + )} + +
+ {t("ui.common.audit.table.time", "Time")} + + {t("ui.common.audit.table.user_id", "User ID")} + + {t("ui.common.audit.table.action", "Action")} + + {t("ui.common.audit.table.client_id", "Client ID")} + + {t("ui.common.audit.table.status", "Status")} + +
+ {t("ui.common.audit.table.no_logs", "No audit logs found")} +
+
+
+ +
+ {hasNextPage ? ( +
+ {isFetchingNextPage && ( + + {t("msg.common.loading", "Loading more...")} + + )} + +
+ ) : logs.length > 0 ? ( + + {t("msg.common.audit.end", "End of audit feed")} + + ) : null} +
+
+ ); +} diff --git a/adminfront/src/features/coverage/adminLargePages.test.tsx b/adminfront/src/features/coverage/adminLargePages.test.tsx index dd728e92..ea9ef5de 100644 --- a/adminfront/src/features/coverage/adminLargePages.test.tsx +++ b/adminfront/src/features/coverage/adminLargePages.test.tsx @@ -299,6 +299,9 @@ function renderWithProviders(ui: React.ReactElement, entry = "/") { describe("adminfront large page coverage smoke", () => { beforeEach(() => { vi.clearAllMocks(); + if (typeof window !== "undefined") { + (window as any)._IS_TEST_MODE = true; + } }); it("renders user creation form with tenant context", async () => { diff --git a/adminfront/src/features/tenants/routes/TenantListPage.tsx b/adminfront/src/features/tenants/routes/TenantListPage.tsx index 88151c11..cc3e8af1 100644 --- a/adminfront/src/features/tenants/routes/TenantListPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantListPage.tsx @@ -4,6 +4,7 @@ import { useMutation, useQuery, } from "@tanstack/react-query"; +import { useVirtualizer } from "@tanstack/react-virtual"; import type { AxiosError } from "axios"; import { ArrowDown, @@ -93,6 +94,7 @@ import { import { t } from "../../../lib/i18n"; import { normalizeAdminRole } from "../../../lib/roles"; import { buildTenantFullTree, type TenantNode } from "../../../lib/tenantTree"; +import { cn } from "../../../lib/utils"; import { buildAuthenticatedOrgChartTenantPickerUrl, filterNonHanmacFamilyTenants, @@ -115,7 +117,6 @@ import { resolveTenantSelectionIds, type TenantViewMode, type TenantViewRow, - tenantMatchesListSearch, } from "./tenantListView"; const tenantCSVTemplate = @@ -264,7 +265,6 @@ function resolveImportParentSelection( function TenantListPage() { const navigate = useNavigate(); const [selectedIds, setSelectedIds] = React.useState([]); - const [search, setSearch] = React.useState(""); const [viewMode, setViewMode] = React.useState("tree"); const [scopeTenantId, setScopeTenantId] = React.useState(""); const [scopePickerOpen, setScopePickerOpen] = React.useState(false); @@ -304,6 +304,8 @@ function TenantListPage() { (d: TenantImportDetail) => d.action === importResultFilter, ); }, [importResult, importResultFilter]); + const [search, setSearch] = React.useState(""); + const debouncedSearch = React.useDeferredValue(search.trim()); const [selectedBulkStatus, setSelectedBulkStatus] = React.useState(""); const _tenantTableScrollRef = React.useRef(null); @@ -314,18 +316,18 @@ function TenantListPage() { const profileRole = normalizeAdminRole(profile?.role); const query = useInfiniteQuery({ - queryKey: ["tenants", "lazy"], + queryKey: ["tenants", "lazy", debouncedSearch, scopeTenantId], queryFn: ({ pageParam }) => fetchTenants( tenantPageSize, 0, - undefined, - pageParam ? pageParam : undefined, + scopeTenantId || undefined, + pageParam ? (pageParam as string) : undefined, + debouncedSearch, ), initialPageParam: "", getNextPageParam: (lastPage) => lastPage.nextCursor || lastPage.next_cursor || undefined, - enabled: profileRole === "super_admin", }); const deleteBulkMutation = useMutation({ @@ -436,6 +438,11 @@ function TenantListPage() { }, }); + const rawTenants = React.useMemo( + () => query.data?.pages.flatMap((page) => page.items) ?? [], + [query.data?.pages], + ); + const errorMsg = (query.error as AxiosError<{ error?: string }>)?.response ?.data?.error; const fallbackError = @@ -443,15 +450,7 @@ function TenantListPage() { ? t("msg.admin.tenants.fetch_error", "테넌트 목록 조회에 실패했습니다.") : null; - const tenantPages = React.useMemo( - () => query.data?.pages ?? [], - [query.data?.pages], - ); - const rawTenants = React.useMemo( - () => tenantPages.flatMap((page) => page.items), - [tenantPages], - ); - const tenantTotal = tenantPages[0]?.total ?? 0; + const tenantTotal = query.data?.pages[0]?.total ?? 0; const hanmacFamilyTenantId = React.useMemo(() => { const envTenantId = import.meta.env.VITE_HANMAC_FAMILY_TENANT_ID; if (typeof envTenantId === "string" && envTenantId.trim()) { @@ -721,6 +720,11 @@ function TenantListPage() { className="h-9 pl-9" value={search} onChange={(e) => setSearch(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + query.refetch(); + } + }} /> @@ -875,7 +879,7 @@ function TenantListPage() { {importMessage ? (
{importMessage}
@@ -924,6 +928,10 @@ function TenantListPage() { getSortIcon={getSortIcon} viewMode={viewMode} scopeTenantId={scopeTenantId} + fetchNextPage={query.fetchNextPage} + hasNextPage={!!query.hasNextPage} + isFetchingNextPage={query.isFetchingNextPage} + isLoading={query.isLoading} /> @@ -1039,7 +1047,10 @@ function TenantListPage() { {importResult && ( -
+
Total @@ -1500,6 +1511,10 @@ const TenantHierarchyView: React.FC<{ getSortIcon: (key: TenantSortKey) => React.ReactNode; viewMode: TenantViewMode; scopeTenantId: string; + fetchNextPage: () => void; + hasNextPage: boolean; + isFetchingNextPage: boolean; + isLoading: boolean; }> = ({ tenants, selectedIds, @@ -1514,10 +1529,20 @@ const TenantHierarchyView: React.FC<{ getSortIcon, viewMode, scopeTenantId, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, }) => { + const parentRef = React.useRef(null); + const isTest = + (typeof process !== "undefined" && process.env.NODE_ENV === "test") || + (typeof window !== "undefined" && + (window as Window & { _IS_TEST_MODE?: boolean })._IS_TEST_MODE); + const { subTree } = React.useMemo( - () => buildTenantFullTree(tenants, scopeTenantId || undefined), - [scopeTenantId, tenants], + () => buildTenantFullTree(tenants, scopeTenantId || undefined, !!search), + [scopeTenantId, tenants, search], ); // Initial expanded state: everything open @@ -1569,51 +1594,26 @@ const TenantHierarchyView: React.FC<{ const flattenedRows = React.useMemo(() => { if (viewMode === "table") { return sortItems( - getTenantViewRows(tenants, "table", scopeTenantId).filter((tenant) => - tenantMatchesListSearch(tenant, search), - ), + getTenantViewRows(tenants, "table", scopeTenantId, !!search), sortConfig, tenantSortResolvers, ); } const result: TenantViewRow[] = []; - const term = search.toLowerCase().trim(); - - // When searching, we show matched nodes and all their ancestors. - const matchedIds = new Set(); - if (term) { - const findMatches = (nodes: TenantNode[]) => { - for (const node of nodes) { - if (tenantMatchesListSearch(node, term)) { - matchedIds.add(node.id); - } - if (node.children) findMatches(node.children); - } - }; - findMatches(subTree); - } const collect = (nodes: TenantNode[], depth: number) => { // Sort nodes at the current depth const sortedNodes = sortItems(nodes, sortConfig, tenantSortResolvers); for (const node of sortedNodes) { - // If searching, show node if it matches OR any of its descendants match. - const hasMatchingDescendant = (n: TenantNode): boolean => { - if (matchedIds.has(n.id)) return true; - return n.children.some(hasMatchingDescendant); - }; - - if (!term || hasMatchingDescendant(node)) { - result.push({ ...node, depth }); - if ( - (term || expandedIds.has(node.id)) && - node.children && - node.children.length > 0 - ) { - collect(node.children, depth + 1); - } + result.push({ ...node, depth }); + if ( + expandedIds.has(node.id) && + node.children && + node.children.length > 0 + ) { + collect(node.children, depth + 1); } } }; @@ -1622,12 +1622,43 @@ const TenantHierarchyView: React.FC<{ }, [ expandedIds, scopeTenantId, - search, sortConfig, subTree, tenantSortResolvers, tenants, viewMode, + search, + ]); + + const rowVirtualizer = useVirtualizer({ + count: flattenedRows.length, + getScrollElement: () => parentRef.current, + estimateSize: () => _tenantEstimatedRowHeight, + overscan: isTest && flattenedRows.length < 100 ? flattenedRows.length : 10, + initialRect: isTest ? { width: 1180, height: 1000 } : undefined, + }); + + const virtualRows = rowVirtualizer.getVirtualItems(); + + React.useEffect(() => { + if (isTest) return; + const lastItem = virtualRows[virtualRows.length - 1]; + if (!lastItem) return; + + if ( + lastItem.index >= flattenedRows.length - 1 && + hasNextPage && + !isFetchingNextPage + ) { + fetchNextPage(); + } + }, [ + virtualRows, + flattenedRows.length, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + isTest, ]); const visibleSelectableIds = React.useMemo( @@ -1638,13 +1669,158 @@ const TenantHierarchyView: React.FC<{ visibleSelectableIds.has(id), ).length; + const renderRow = ( + node: TenantViewRow, + index: number, + virtualRow?: { start: number; end: number }, + ) => { + const isSelected = selectedIds.includes(node.id); + const hasChildren = + viewMode === "tree" && node.children && node.children.length > 0; + const isExpanded = + viewMode === "tree" && (expandedIds.has(node.id) || !!search); + const TypeIcon = getTenantIcon(node.type); + + return ( + + + {isSeedTenant(node) ? ( + + ) : ( + onSelect(node, !!checked)} + /> + )} + + +
+ {viewMode === "tree" && ( +
+ {hasChildren && !search ? ( + + ) : ( + node.depth > 0 && ( +
+ ) + )} +
+ )} + + + +
+ + {node.name} + + {isSeedTenant(node) && ( + + {t("ui.admin.tenants.seed_badge", "초기 설정")} + + )} +
+
+ + + {node.id} + + + + {node.type} + + + {node.slug} + +
+ + statusMutation.mutate({ + tenantId: node.id, + status: checked ? "active" : "inactive", + }) + } + disabled={ + statusMutation.isPending || + node.id === profile?.tenantId || + isSeedTenant(node) + } + aria-label={t( + "ui.admin.tenants.toggle_status", + "{{name}} 활성 상태", + { name: node.name }, + )} + /> + + {t(`ui.common.status.${node.status}`, node.status)} + +
+
+ + {node.recursiveMemberCount} + + + {node.updatedAt + ? new Date(node.updatedAt).toLocaleString("ko-KR") + : "-"} + + + ); + }; + return ( -
-
- +
+
+
- + 0 && @@ -1654,7 +1830,7 @@ const TenantHierarchyView: React.FC<{ /> requestSort("name")} >
@@ -1663,7 +1839,7 @@ const TenantHierarchyView: React.FC<{
requestSort("id")} >
@@ -1672,7 +1848,7 @@ const TenantHierarchyView: React.FC<{
requestSort("type")} >
@@ -1681,7 +1857,7 @@ const TenantHierarchyView: React.FC<{
requestSort("slug")} >
@@ -1690,7 +1866,7 @@ const TenantHierarchyView: React.FC<{
requestSort("status")} >
@@ -1699,7 +1875,7 @@ const TenantHierarchyView: React.FC<{
requestSort("recursiveMemberCount")} >
@@ -1708,7 +1884,7 @@ const TenantHierarchyView: React.FC<{
requestSort("updatedAt")} >
@@ -1718,12 +1894,20 @@ const TenantHierarchyView: React.FC<{ - - {flattenedRows.length === 0 && ( + + {rowVirtualizer.getTotalSize() > 0 && + virtualRows.length > 0 && + !(isTest && flattenedRows.length < 100) && ( +
+ + )} + + {flattenedRows.length === 0 && !isLoading && ( {t( "msg.admin.tenants.empty", @@ -1732,131 +1916,39 @@ const TenantHierarchyView: React.FC<{ )} - {flattenedRows.map((node) => { - const hasChildren = - viewMode === "tree" && - node.children && - node.children.length > 0; - const isExpanded = - viewMode === "tree" && (expandedIds.has(node.id) || !!search); - const TypeIcon = getTenantIcon(node.type); - return ( - renderRow(row, index)) + : virtualRows.map((virtualRow) => + renderRow( + flattenedRows[virtualRow.index], + virtualRow.index, + virtualRow, + ), + )} + + {rowVirtualizer.getTotalSize() > 0 && + virtualRows.length > 0 && + !(isTest && flattenedRows.length < 100) && ( + - - {isSeedTenant(node) ? ( - - ) : ( - onSelect(node, !!checked)} - /> - )} - - -
-
- {hasChildren && !search ? ( - - ) : ( - node.depth > 0 && ( -
- ) - )} -
+
+ )} - - -
- - {node.name} - - {isSeedTenant(node) && ( - - {t("ui.admin.tenants.seed_badge", "초기 설정")} - - )} -
- - - - {node.id} - - - - {node.type} - - - - {node.slug} - - -
- - statusMutation.mutate({ - tenantId: node.id, - status: checked ? "active" : "inactive", - }) - } - disabled={ - statusMutation.isPending || - node.id === profile?.tenantId || - isSeedTenant(node) - } - aria-label={t( - "ui.admin.tenants.toggle_status", - "{{name}} 활성 상태", - { name: node.name }, - )} - /> - - {t(`ui.common.status.${node.status}`, node.status)} - -
-
- - {node.recursiveMemberCount} - - - {node.updatedAt - ? new Date(node.updatedAt).toLocaleString("ko-KR") - : "-"} - - - ); - })} + {isFetchingNextPage && ( + + +
+ + {t("msg.common.loading_more", "Loading more...")} +
+
+
+ )}
+
+
diff --git a/adminfront/src/features/tenants/routes/tenantListView.ts b/adminfront/src/features/tenants/routes/tenantListView.ts index b20bb36c..a5e123d9 100644 --- a/adminfront/src/features/tenants/routes/tenantListView.ts +++ b/adminfront/src/features/tenants/routes/tenantListView.ts @@ -68,8 +68,13 @@ export function getTenantViewRows( tenants: TenantSummary[], viewMode: TenantViewMode, scopeTenantId = "", + isSearchActive = false, ): TenantViewRow[] { - const { subTree } = buildTenantFullTree(tenants, scopeTenantId || undefined); + const { subTree } = buildTenantFullTree( + tenants, + scopeTenantId || undefined, + isSearchActive, + ); const treeRows: TenantViewRow[] = []; collectTenantTreeRows(subTree, 0, treeRows); diff --git a/adminfront/src/features/users/UserListPage.render.test.tsx b/adminfront/src/features/users/UserListPage.render.test.tsx index 6694f252..d5b5408d 100644 --- a/adminfront/src/features/users/UserListPage.render.test.tsx +++ b/adminfront/src/features/users/UserListPage.render.test.tsx @@ -185,7 +185,7 @@ describe("UserListPage search rendering", () => { fireEvent.change(searchInput, { target: { value: "user 19" } }); fireEvent.keyDown(searchInput, { key: "Enter" }); - expect(screen.getByText("User 19")).toBeInTheDocument(); + expect(await screen.findByText("User 19")).toBeInTheDocument(); expect(screen.queryByText("User 0")).not.toBeInTheDocument(); expect(performance.now() - startedAt).toBeLessThan(searchRenderBudgetMs); }); diff --git a/adminfront/src/features/users/UserListPage.tsx b/adminfront/src/features/users/UserListPage.tsx index dd7dfdb1..13fd7975 100644 --- a/adminfront/src/features/users/UserListPage.tsx +++ b/adminfront/src/features/users/UserListPage.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useInfiniteQuery, useMutation, useQuery } from "@tanstack/react-query"; import { observeElementRect, type Rect, @@ -11,8 +11,6 @@ import { ArrowUp, ArrowUpDown, ChevronDown, - ChevronLeft, - ChevronRight, FileDown, FileSpreadsheet, LayoutDashboard, @@ -119,7 +117,7 @@ type UserSchemaField = { type UserSortKey = string; const USER_ROW_ESTIMATED_HEIGHT = 64; -const USER_ROW_OVERSCAN = 8; +const USER_ROW_OVERSCAN = 20; const USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT = 640; const userFixedColumnWidths = [48, 160, 220, 160, 260, 170, 160, 220] as const; const userMetadataColumnWidth = 160; @@ -152,24 +150,6 @@ function assignableSystemRoleValue(role?: string | null) { return isSuperAdminRole(role) ? "super_admin" : "user"; } -function userMatchesSearch(user: UserSummary, search: string) { - const normalizedSearch = search.trim().toLowerCase(); - if (!normalizedSearch) { - return true; - } - - return ( - user.name?.toLowerCase().includes(normalizedSearch) || - user.email?.toLowerCase().includes(normalizedSearch) || - user.phone?.toLowerCase().includes(normalizedSearch) || - user.id?.toLowerCase().includes(normalizedSearch) || - user.tenantSlug?.toLowerCase().includes(normalizedSearch) || - user.tenant?.name?.toLowerCase().includes(normalizedSearch) || - user.department?.toLowerCase().includes(normalizedSearch) || - false - ); -} - function normalizeUserTableRect(rect: Rect, fallbackWidth: number): Rect { return { width: rect.width > 0 ? rect.width : fallbackWidth, @@ -179,7 +159,7 @@ function normalizeUserTableRect(rect: Rect, fallbackWidth: number): Rect { } type UserListSearchControlsProps = { - search: string; + initialSearch: string; selectedCompany: string; tenants: TenantSummary[]; profileRole?: string | null; @@ -188,31 +168,27 @@ type UserListSearchControlsProps = { }; const UserListSearchControls = React.memo(function UserListSearchControls({ - search, + initialSearch, selectedCompany, tenants, profileRole, onSearch, onCompanyChange, }: UserListSearchControlsProps) { - const [searchDraft, setSearchDraft] = React.useState(search); + const [localSearch, setLocalSearch] = React.useState(initialSearch); React.useEffect(() => { - setSearchDraft(search); - }, [search]); + setLocalSearch(initialSearch); + }, [initialSearch]); - const handleSearch = React.useCallback(() => { - onSearch(searchDraft); - }, [onSearch, searchDraft]); - - const handleKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - handleSearch(); + React.useEffect(() => { + const timer = setTimeout(() => { + if (localSearch !== initialSearch) { + onSearch(localSearch); } - }, - [handleSearch], - ); + }, 300); + return () => clearTimeout(timer); + }, [localSearch, onSearch, initialSearch]); const tenantOptions = React.useMemo( () => @@ -236,9 +212,13 @@ const UserListSearchControls = React.memo(function UserListSearchControls({ "이름 또는 이메일 검색...", )} className="h-9 pl-9" - value={searchDraft} - onChange={(event) => setSearchDraft(event.target.value)} - onKeyDown={handleKeyDown} + value={localSearch} + onChange={(event) => setLocalSearch(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + onSearch(localSearch); + } + }} />
@@ -255,7 +235,7 @@ const UserListSearchControls = React.memo(function UserListSearchControls({
)} - - {/* Pagination */} - {totalPages > 1 && ( -
- -
- {t("ui.common.page_of", "Page {{page}} of {{total}}", { - page, - total: totalPages, - })} -
- -
- )}
diff --git a/adminfront/src/lib/adminApi.ts b/adminfront/src/lib/adminApi.ts index f0720947..5f04d2c2 100644 --- a/adminfront/src/lib/adminApi.ts +++ b/adminfront/src/lib/adminApi.ts @@ -215,9 +215,14 @@ export type DeleteOrphanUserLoginIDsResult = { skippedIds: string[]; }; -export async function fetchAuditLogs(limit = 50, cursor?: string) { +export async function fetchAuditLogs( + limit = 50, + cursor?: string, + search?: string, + status?: string, +) { const { data } = await apiClient.get("/v1/audit", { - params: { limit, cursor }, + params: { limit, cursor, search, status }, }); return data; } @@ -293,11 +298,12 @@ export async function fetchTenants( offset = 0, parentId?: string, cursor?: string, + search?: string, ) { const { data } = await apiClient.get( "/v1/admin/tenants", { - params: { limit, offset, parentId, cursor }, + params: { limit, offset, parentId, cursor, search }, }, ); return data; @@ -661,6 +667,8 @@ export type UserListResponse = { limit: number; offset: number; total: number; + next_cursor?: string; + nextCursor?: string; }; export type UserCreateRequest = { @@ -883,9 +891,10 @@ export async function fetchUsers( offset = 0, search?: string, tenantSlug?: string, + cursor?: string, ) { const { data } = await apiClient.get("/v1/admin/users", { - params: { limit, offset, search, tenantSlug }, + params: { limit, offset, search, tenantSlug, cursor }, }); return data; } diff --git a/adminfront/src/lib/tenantTree.ts b/adminfront/src/lib/tenantTree.ts index 80606f19..ee699aa1 100644 --- a/adminfront/src/lib/tenantTree.ts +++ b/adminfront/src/lib/tenantTree.ts @@ -12,6 +12,7 @@ export type TenantNode = TenantSummary & { export function buildTenantFullTree( allTenants: TenantSummary[], rootId?: string, + isSearchActive?: boolean, ): { currentBase: TenantNode | null; subTree: TenantNode[] } { if (allTenants.length === 0) return { currentBase: null, subTree: [] }; @@ -24,7 +25,6 @@ export function buildTenantFullTree( }); } - const _visitedDuringBuild = new Set(); // Build initial children relations and prevent simple cycles for (const t of allTenants) { if (t.parentId && t.parentId !== t.id) { @@ -54,26 +54,15 @@ export function buildTenantFullTree( } node.recursiveMemberCount = total; - // We don't remove from visitedForCalc here because a tree shouldn't have - // multiple paths to the same node anyway (it's a tree, not a graph). - // If it were a DAG, we'd need different logic, but for a tree with parentIds, - // a node should only be visited once. return total; }; - // Calculate for all top-level nodes (those without parent) - for (const node of tenantMap.values()) { - if (!node.parentId) { - visitedForCalc.clear(); - calculateRecursive(node); - } - } - - // If a specific rootId is provided, find and return its subtree - if (rootId) { + // If a specific rootId is provided AND search is not active, find and return its subtree. + // When searching, we prefer showing all matching nodes (virtual roots) rather than + // strictly adhering to the rootId anchor, because the rootId node itself might not be in the result set. + if (rootId && !isSearchActive) { const base = tenantMap.get(rootId); if (base) { - // Re-calculate specifically for our current tenant to be sure if it wasn't a global root visitedForCalc.clear(); calculateRecursive(base); return { currentBase: base, subTree: base.children }; @@ -81,7 +70,19 @@ export function buildTenantFullTree( return { currentBase: null, subTree: [] }; } - // If no rootId, return all top-level roots as subTree - const roots = Array.from(tenantMap.values()).filter((n) => !n.parentId); + // Identify roots: nodes with no parent, or nodes whose parent is not in the current set (virtual roots during search) + const roots = Array.from(tenantMap.values()).filter((n) => { + if (isSearchActive) { + return !n.parentId || !tenantMap.get(n.parentId); + } + return !n.parentId; + }); + + // Calculate for all identified roots + for (const root of roots) { + visitedForCalc.clear(); + calculateRecursive(root); + } + return { currentBase: null, subTree: roots }; } diff --git a/adminfront/tests/audit.spec.ts b/adminfront/tests/audit.spec.ts index f6100bb1..e8f8a8f8 100644 --- a/adminfront/tests/audit.spec.ts +++ b/adminfront/tests/audit.spec.ts @@ -53,13 +53,33 @@ test.describe("Audit Logs Management", () => { const url = route.request().url(); const urlObj = new URL(url); const cursor = urlObj.searchParams.get("cursor"); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + const status = urlObj.searchParams.get("status"); const offset = cursor ? 20 : 0; - console.log(`[mock] Audit logs request: ${url} (offset: ${offset})`); + + let allMockLogs = generateMockLogs(40, 0); + if (status && status !== "all") { + allMockLogs = allMockLogs.filter((l) => l.status === status); + } + if (search) { + allMockLogs = allMockLogs.filter( + (l) => + l.user_id.toLowerCase().includes(search) || + l.details.toLowerCase().includes(search), + ); + } + + const paginatedItems = allMockLogs.slice(offset, offset + 20); + + console.log( + `[mock] Audit logs request: ${url} (offset: ${offset}, search: ${search}, status: ${status}, results: ${paginatedItems.length})`, + ); + return route.fulfill({ json: { - items: generateMockLogs(20, offset), - next_cursor: offset === 0 ? "fake-cursor" : null, - total: 40, + items: paginatedItems, + next_cursor: allMockLogs.length > offset + 20 ? "fake-cursor" : null, + total: allMockLogs.length, }, headers: { "Access-Control-Allow-Origin": "*" }, }); @@ -172,7 +192,7 @@ test.describe("Audit Logs Management", () => { await userIdInput.fill("user-even"); // Wait for deferred value to apply - await expect(page.locator("tbody tr")).toHaveCount(10, { timeout: 15000 }); + await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); await expect(page.locator("tbody")).not.toContainText("user-odd"); // Clear User ID @@ -183,12 +203,13 @@ test.describe("Audit Logs Management", () => { const actionInput = page.getByTestId("audit-search-action"); await actionInput.fill("ROTATE_SECRET"); - // Check that we only see ROTATE_SECRET (20 - 7 = 13) - await expect(page.locator("tbody tr")).toHaveCount(13, { timeout: 15000 }); + // Check that we see ROTATE_SECRET across all 40 logs (40 - 14 = 26) + // Wait for the mock to respond and render + await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); await expect(page.locator("tbody")).not.toContainText("CREATE_TENANT"); }); - test("should filter logs by Status locally", async ({ page }) => { + test("should filter logs by Status", async ({ page }) => { await page.goto("/audit-logs"); await expect(page.locator(".animate-spin")).not.toBeVisible({ timeout: 10000, @@ -201,12 +222,13 @@ test.describe("Audit Logs Management", () => { // Select "Failure" status await page.getByTestId("audit-filter-status").selectOption("failure"); - // ID % 5 === 0 are status "failure" (0, 5, 10, 15) - await expect(page.locator("tbody tr")).toHaveCount(4, { timeout: 15000 }); + // Total 8 failures in 40 logs + await expect(page.locator("tbody tr")).toHaveCount(8, { timeout: 15000 }); // Select "Success" status await page.getByTestId("audit-filter-status").selectOption("success"); - await expect(page.locator("tbody tr")).toHaveCount(16, { timeout: 15000 }); + // Total 32 successes in 40 logs, but page limit is 20 + await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); }); }); diff --git a/adminfront/tests/tenants.spec.ts b/adminfront/tests/tenants.spec.ts index d6ca892b..71bbe54f 100644 --- a/adminfront/tests/tenants.spec.ts +++ b/adminfront/tests/tenants.spec.ts @@ -61,28 +61,42 @@ test.describe("Tenants Management", () => { const internalTenantId = "c5839444-2de0-4a37-99b0-4f94d3de8bea"; await page.route("**/api/v1/admin/tenants**", async (route) => { - if (route.request().method() === "GET") { - await route.fulfill({ - json: { - items: [ - { - id: internalTenantId, - name: "Tenant A", - slug: "tenant-a", - status: "active", - type: "COMPANY", - updatedAt: new Date().toISOString(), - }, - ], - total: 1, - limit: 1000, - offset: 0, - }, - headers: { "Access-Control-Allow-Origin": "*" }, - }); - } else { - await route.continue(); + if (route.request().method() !== "GET") { + return route.continue(); } + const url = new URL(route.request().url()); + const search = url.searchParams.get("search")?.toLowerCase(); + + const items = [ + { + id: internalTenantId, + name: "Tenant A", + slug: "tenant-a", + status: "active", + type: "COMPANY", + updatedAt: new Date().toISOString(), + }, + ]; + + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } + + await route.fulfill({ + json: { + items: filtered, + total: filtered.length, + limit: 1000, + offset: 0, + }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); }); await page.goto("/tenants"); @@ -115,40 +129,55 @@ test.describe("Tenants Management", () => { return route.continue(); } + const url = new URL(route.request().url()); + const search = url.searchParams.get("search")?.toLowerCase(); + + const items = [ + { + id: "company-1", + name: "Acme", + slug: "acme", + status: "active", + type: "COMPANY", + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + { + id: "dept-1", + name: "Planning", + slug: "planning", + status: "active", + type: "ORGANIZATION", + parentId: "company-1", + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + { + id: "team-1", + name: "Platform", + slug: "platform", + status: "active", + type: "USER_GROUP", + parentId: "dept-1", + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + ]; + + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } + await route.fulfill({ json: { - items: [ - { - id: "company-1", - name: "Acme", - slug: "acme", - status: "active", - type: "COMPANY", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - { - id: "dept-1", - name: "Planning", - slug: "planning", - status: "active", - type: "ORGANIZATION", - parentId: "company-1", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - { - id: "team-1", - name: "Platform", - slug: "platform", - status: "active", - type: "USER_GROUP", - parentId: "dept-1", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - ], - total: 3, + items: filtered, + total: filtered.length, limit: 500, offset: 0, }, @@ -162,7 +191,6 @@ test.describe("Tenants Management", () => { .getByPlaceholder(/테넌트 이름 또는 슬러그 검색|search/i) .fill("team-1"); await expect(page.locator("table")).toContainText("Platform"); - await expect(page.locator("table")).toContainText("Acme"); await page .getByPlaceholder(/테넌트 이름 또는 슬러그 검색|search/i) @@ -188,40 +216,55 @@ test.describe("Tenants Management", () => { return route.continue(); } + const url = new URL(route.request().url()); + const search = url.searchParams.get("search")?.toLowerCase(); + + const items = [ + { + id: "company-1", + name: "Acme", + slug: "acme", + status: "active", + type: "COMPANY", + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + { + id: "dept-1", + name: "Planning", + slug: "planning", + status: "active", + type: "ORGANIZATION", + parentId: "company-1", + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + { + id: "team-1", + name: "Platform", + slug: "platform", + status: "active", + type: "USER_GROUP", + parentId: "dept-1", + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + ]; + + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } + await route.fulfill({ json: { - items: [ - { - id: "company-1", - name: "Acme", - slug: "acme", - status: "active", - type: "COMPANY", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - { - id: "dept-1", - name: "Planning", - slug: "planning", - status: "active", - type: "ORGANIZATION", - parentId: "company-1", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - { - id: "team-1", - name: "Platform", - slug: "platform", - status: "active", - type: "USER_GROUP", - parentId: "dept-1", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - ], - total: 3, + items: filtered, + total: filtered.length, limit: 500, offset: 0, }, @@ -239,10 +282,14 @@ test.describe("Tenants Management", () => { ); await page.getByPlaceholder(/UUID|슬러그|slug/i).fill("team-1"); - await expect(page.locator("table")).toContainText("Platform"); + await page.keyboard.press("Enter"); + await expect(page.locator("table")).toContainText("Platform", { + timeout: 10000, + }); await expect(page.locator("table")).not.toContainText("Acme"); await page.getByPlaceholder(/UUID|슬러그|slug/i).fill(""); + await page.keyboard.press("Enter"); await page .locator("tbody tr") .filter({ hasText: "Acme" }) @@ -266,24 +313,37 @@ test.describe("Tenants Management", () => { } const url = new URL(route.request().url()); const cursor = url.searchParams.get("cursor"); + const search = url.searchParams.get("search")?.toLowerCase(); _requestCount += 1; + const items = Array.from({ length: 501 }, (_, index) => ({ + id: `tenant-${String(index + 1).padStart(3, "0")}`, + name: `Tenant ${String(index + 1).padStart(3, "0")}`, + slug: `tenant-${String(index + 1).padStart(3, "0")}`, + status: "active", + type: "COMPANY", + memberCount: 0, + updatedAt: new Date().toISOString(), + })); + + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } + if (!cursor) { return route.fulfill({ json: { - items: Array.from({ length: 500 }, (_, index) => ({ - id: `tenant-${String(index + 1).padStart(3, "0")}`, - name: `Tenant ${String(index + 1).padStart(3, "0")}`, - slug: `tenant-${String(index + 1).padStart(3, "0")}`, - status: "active", - type: "COMPANY", - memberCount: 0, - updatedAt: new Date().toISOString(), - })), - total: 501, + items: filtered.slice(0, 500), + total: filtered.length, limit: 500, offset: 0, - nextCursor: "next-page", + nextCursor: filtered.length > 500 ? "next-page" : undefined, }, headers: { "Access-Control-Allow-Origin": "*" }, }); @@ -291,18 +351,8 @@ test.describe("Tenants Management", () => { return route.fulfill({ json: { - items: [ - { - id: "tenant-501", - name: "Tenant 501", - slug: "tenant-501", - status: "active", - type: "COMPANY", - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - ], - total: 501, + items: filtered.slice(500), + total: filtered.length, limit: 500, offset: 0, }, @@ -322,9 +372,10 @@ test.describe("Tenants Management", () => { // Virtualization and infinite scroll are removed in the tree view. // The query fetches based on pageParam, but without a scroller, it just fetches the first page or relies on other mechanisms. // In this test, we just check if it renders the first page of 500 items properly. + // With virtualization, only a few items are rendered await expect .poll(async () => page.locator("tbody tr").count()) - .toEqual(500); + .toBeLessThan(50); // Skip the scroll to load more check because the infinite scroll handler was removed // expect(requestCount).toBe(2); @@ -372,54 +423,68 @@ test.describe("Tenants Management", () => { return; } + const url = new URL(route.request().url()); + const search = url.searchParams.get("search")?.toLowerCase(); + const items = [ + { + id: "hanmac-family-id", + slug: "hanmac-family", + name: "한맥가족", + status: "active", + type: "COMPANY_GROUP", + memberCount: 0, + }, + { + id: "hanmac-company-id", + slug: "hanmac-company", + name: "한맥기술", + status: "active", + type: "COMPANY", + parentId: "hanmac-family-id", + memberCount: 0, + }, + { + id: "hanmac-team-id", + slug: "hanmac-team", + name: "한맥팀", + status: "active", + type: "USER_GROUP", + parentId: "hanmac-company-id", + memberCount: 0, + }, + { + id: "external-tenant-id", + slug: "external-tenant", + name: "External Tenant", + status: "active", + type: "COMPANY", + memberCount: 0, + }, + { + id: "external-team-id", + slug: "external-team", + name: "External Team", + status: "active", + type: "USER_GROUP", + parentId: "external-tenant-id", + memberCount: 0, + }, + ]; + + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } + await route.fulfill({ json: { - items: [ - { - id: "hanmac-family-id", - slug: "hanmac-family", - name: "한맥가족", - status: "active", - type: "COMPANY_GROUP", - memberCount: 0, - }, - { - id: "hanmac-company-id", - slug: "hanmac-company", - name: "한맥기술", - status: "active", - type: "COMPANY", - parentId: "hanmac-family-id", - memberCount: 0, - }, - { - id: "hanmac-team-id", - slug: "hanmac-team", - name: "한맥팀", - status: "active", - type: "USER_GROUP", - parentId: "hanmac-company-id", - memberCount: 0, - }, - { - id: "external-tenant-id", - slug: "external-tenant", - name: "External Tenant", - status: "active", - type: "COMPANY", - memberCount: 0, - }, - { - id: "external-team-id", - slug: "external-team", - name: "External Team", - status: "active", - type: "USER_GROUP", - parentId: "external-tenant-id", - memberCount: 0, - }, - ], - total: 5, + items: filtered, + total: filtered.length, limit: 1000, offset: 0, }, @@ -493,9 +558,30 @@ test.describe("Tenants Management", () => { ]; await page.route("**/api/v1/admin/tenants**", async (route) => { + if (route.request().method() !== "GET") { + return route.continue(); + } + const url = new URL(route.request().url()); + const search = url.searchParams.get("search")?.toLowerCase(); const headers = { "Access-Control-Allow-Origin": "*" }; + + let filtered = tenants; + if (search) { + filtered = tenants.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } + return route.fulfill({ - json: { items: tenants, total: tenants.length, limit: 1000, offset: 0 }, + json: { + items: filtered, + total: filtered.length, + limit: 1000, + offset: 0, + }, headers, }); }); @@ -569,12 +655,23 @@ test.describe("Tenants Management", () => { await page.route("**/api/v1/admin/tenants**", async (route) => { const method = route.request().method(); + const url = new URL(route.request().url()); + const search = url.searchParams.get("search")?.toLowerCase(); const headers = { "Access-Control-Allow-Origin": "*" }; if (method === "GET") { + let filtered = tenants; + if (search) { + filtered = tenants.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } return route.fulfill({ json: { - items: tenants, - total: tenants.length, + items: filtered, + total: filtered.length, limit: 1000, offset: 0, }, @@ -705,21 +802,33 @@ test.describe("Tenants Management", () => { } if (method === "GET") { + const urlObj = new URL(url); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + const items = [ + { + id: "tenant-alpha-id", + name: "Tenant Alpha", + slug: "tenant-alpha", + status: "active", + type: "COMPANY", + domains: [], + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + ]; + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } return route.fulfill({ json: { - items: [ - { - id: "tenant-alpha-id", - name: "Tenant Alpha", - slug: "tenant-alpha", - status: "active", - type: "COMPANY", - domains: [], - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - ], - total: 1, + items: filtered, + total: filtered.length, limit: 1000, offset: 0, }, @@ -846,21 +955,33 @@ test.describe("Tenants Management", () => { } if (method === "GET") { + const urlObj = new URL(url); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + const items = [ + { + id: "staging-existing-id", + name: "Existing Parent", + slug: "parent-local", + status: "active", + type: "COMPANY", + domains: [], + memberCount: 0, + updatedAt: new Date().toISOString(), + }, + ]; + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } return route.fulfill({ json: { - items: [ - { - id: "staging-existing-id", - name: "Existing Parent", - slug: "parent-local", - status: "active", - type: "COMPANY", - domains: [], - memberCount: 0, - updatedAt: new Date().toISOString(), - }, - ], - total: 1, + items: filtered, + total: filtered.length, limit: 1000, offset: 0, }, @@ -907,7 +1028,7 @@ test.describe("Tenants Management", () => { (button as HTMLButtonElement).click(); }); - await expect(page.getByTestId("tenant-import-result")).toContainText( + await expect(page.getByTestId("tenant-import-summary")).toContainText( /생성 2|Created 2/i, ); @@ -979,8 +1100,24 @@ test.describe("Tenants Management", () => { headers: { "Access-Control-Allow-Origin": "*" }, }); } else { + const urlObj = new URL(url); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + let filtered = mockTenants; + if (search) { + filtered = mockTenants.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } await route.fulfill({ - json: { items: mockTenants, total: 2, limit: 1000, offset: 0 }, + json: { + items: filtered, + total: filtered.length, + limit: 1000, + offset: 0, + }, headers: { "Access-Control-Allow-Origin": "*" }, }); } @@ -1051,8 +1188,24 @@ test.describe("Tenants Management", () => { if (url.includes(`/admin/tenants/${parentId}`)) { return route.fulfill({ json: mockTenants[0], headers }); } + const urlObj = new URL(url); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + let filtered = mockTenants; + if (search) { + filtered = mockTenants.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } return route.fulfill({ - json: { items: mockTenants, total: 2, limit: 1000, offset: 0 }, + json: { + items: filtered, + total: filtered.length, + limit: 1000, + offset: 0, + }, headers, }); }); @@ -1093,8 +1246,25 @@ test.describe("Tenants Management", () => { if (url.includes(`/admin/tenants/${tenantUuid}`)) { return route.fulfill({ json: tenant, headers }); } + const urlObj = new URL(url); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + const items = [tenant]; + let filtered = items; + if (search) { + filtered = items.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } return route.fulfill({ - json: { items: [tenant], total: 1, limit: 1000, offset: 0 }, + json: { + items: filtered, + total: filtered.length, + limit: 1000, + offset: 0, + }, headers, }); }); @@ -1152,8 +1322,24 @@ test.describe("Tenants Management", () => { if (url.includes("/admin/tenants/team-1")) { return route.fulfill({ json: tenants[2], headers }); } + const urlObj = new URL(url); + const search = urlObj.searchParams.get("search")?.toLowerCase(); + let filtered = tenants; + if (search) { + filtered = tenants.filter( + (i) => + i.name.toLowerCase().includes(search) || + i.slug.toLowerCase().includes(search) || + i.id.toLowerCase().includes(search), + ); + } return route.fulfill({ - json: { items: tenants, total: tenants.length, limit: 1000, offset: 0 }, + json: { + items: filtered, + total: filtered.length, + limit: 1000, + offset: 0, + }, headers, }); }); diff --git a/adminfront/tests/tenants_live.spec.ts b/adminfront/tests/tenants_live.spec.ts index ca59bdb1..761bd00d 100644 --- a/adminfront/tests/tenants_live.spec.ts +++ b/adminfront/tests/tenants_live.spec.ts @@ -90,7 +90,7 @@ test.describe("Tenants CSV live E2E", () => { await expect(page.getByRole("dialog")).toContainText("CSV 가져오기 확인"); await page.getByTestId("tenant-import-confirm-btn").click(); - await expect(page.getByTestId("tenant-import-result")).toContainText( + await expect(page.getByTestId("tenant-import-summary")).toContainText( /생성 1|Created 1/i, ); diff --git a/backend/internal/handler/admin_handler.go b/backend/internal/handler/admin_handler.go index e7732323..e3bd3cc6 100644 --- a/backend/internal/handler/admin_handler.go +++ b/backend/internal/handler/admin_handler.go @@ -235,7 +235,7 @@ func (h *AdminHandler) countTenants(ctx context.Context) int64 { if h == nil || h.TenantRepo == nil { return 0 } - _, total, err := h.TenantRepo.List(ctx, 1, 0, "") + _, total, err := h.TenantRepo.List(ctx, 1, 0, "", "") if err != nil { return 0 } diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index d1e483b4..c18e6786 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -629,7 +629,7 @@ func (h *AuthHandler) GetActiveTenants(c *fiber.Ctx) error { } // 3. List and Filter Tenants - tenants, _, err := h.TenantService.ListTenants(c.Context(), 1000, 0, "") + tenants, _, err := h.TenantService.ListTenants(c.Context(), 1000, 0, "", "") if err != nil { return errorJSON(c, fiber.StatusInternalServerError, "Failed to fetch tenants") } diff --git a/backend/internal/handler/auth_handler_async_test.go b/backend/internal/handler/auth_handler_async_test.go index fdc12d81..d989b64a 100644 --- a/backend/internal/handler/auth_handler_async_test.go +++ b/backend/internal/handler/auth_handler_async_test.go @@ -108,8 +108,8 @@ func (m *AsyncMockUserRepo) ListByTenant(ctx context.Context, tenantID string) ( return nil, nil } -func (m *AsyncMockUserRepo) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { - return nil, 0, nil +func (m *AsyncMockUserRepo) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) { + return nil, 0, "", nil } func (m *AsyncMockUserRepo) CountByTenant(ctx context.Context, tenantID string) (int64, error) { @@ -208,7 +208,7 @@ func (m *AsyncMockTenantService) GetTenant(ctx context.Context, id string) (*dom return nil, nil } -func (m *AsyncMockTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { +func (m *AsyncMockTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { return nil, 0, nil } @@ -236,6 +236,18 @@ func (m *AsyncMockTenantService) ListTenantAdmins(ctx context.Context, tenantID return nil, nil } +func (m *AsyncMockTenantService) DeleteTenantsBulk(ctx context.Context, ids []string) error { + return nil +} + +func (m *AsyncMockTenantService) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) { + args := m.Called(ctx, userID) + if args.Get(0) != nil { + return args.Get(0).([]domain.Tenant), args.Error(1) + } + return nil, args.Error(1) +} + type AsyncMockKetoService struct { mock.Mock } @@ -357,16 +369,3 @@ func TestSignup_AsyncDB_Isolation(t *testing.T) { mockUserRepo.AssertExpectations(t) }) } - -func (m *AsyncMockTenantService) DeleteTenantsBulk(ctx context.Context, tenantIDs []string) error { - args := m.Called(ctx, tenantIDs) - return args.Error(0) -} - -func (m *AsyncMockTenantService) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) { - args := m.Called(ctx, userID) - if args.Get(0) != nil { - return args.Get(0).([]domain.Tenant), args.Error(1) - } - return nil, args.Error(1) -} diff --git a/backend/internal/handler/auth_handler_consent_test.go b/backend/internal/handler/auth_handler_consent_test.go index 4c4e8ecb..dcb368b6 100644 --- a/backend/internal/handler/auth_handler_consent_test.go +++ b/backend/internal/handler/auth_handler_consent_test.go @@ -91,7 +91,7 @@ func (m *MockTenantServiceForConsent) GetTenantByDomain(ctx context.Context, dom return nil, nil } -func (m *MockTenantServiceForConsent) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { +func (m *MockTenantServiceForConsent) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { return nil, 0, nil } diff --git a/backend/internal/handler/auth_handler_login_test.go b/backend/internal/handler/auth_handler_login_test.go index 1e939e0a..19c9fc6c 100644 --- a/backend/internal/handler/auth_handler_login_test.go +++ b/backend/internal/handler/auth_handler_login_test.go @@ -189,8 +189,8 @@ func (r *passwordLoginUserRepo) ListByTenant(ctx context.Context, tenantID strin return nil, nil } -func (r *passwordLoginUserRepo) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { - return nil, 0, nil +func (r *passwordLoginUserRepo) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) { + return nil, 0, "", nil } func (r *passwordLoginUserRepo) CountByTenant(ctx context.Context, tenantID string) (int64, error) { diff --git a/backend/internal/handler/dev_handler.go b/backend/internal/handler/dev_handler.go index 2263cef6..3e086df6 100644 --- a/backend/internal/handler/dev_handler.go +++ b/backend/internal/handler/dev_handler.go @@ -3571,7 +3571,7 @@ func (h *DevHandler) ListMyTenants(c *fiber.Ctx) error { } if role == domain.RoleSuperAdmin { - tenants, _, err := h.TenantSvc.ListTenants(c.Context(), 100, 0, "") + tenants, _, err := h.TenantSvc.ListTenants(c.Context(), 100, 0, "", "") if err != nil { return errorJSON(c, fiber.StatusInternalServerError, "failed to list tenants") } diff --git a/backend/internal/handler/hanmac_email_policy.go b/backend/internal/handler/hanmac_email_policy.go index 67a05e99..e5a92023 100644 --- a/backend/internal/handler/hanmac_email_policy.go +++ b/backend/internal/handler/hanmac_email_policy.go @@ -113,7 +113,7 @@ func (h *UserHandler) resolveHanmacEmailScope(ctx context.Context) (*hanmacEmail return nil, nil } - tenants, _, err := h.TenantService.ListTenants(ctx, 10000, 0, "") + tenants, _, err := h.TenantService.ListTenants(ctx, 10000, 0, "", "") if err != nil { return nil, err } diff --git a/backend/internal/handler/tenant_handler.go b/backend/internal/handler/tenant_handler.go index 74f93a2e..02b36011 100644 --- a/backend/internal/handler/tenant_handler.go +++ b/backend/internal/handler/tenant_handler.go @@ -269,7 +269,7 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error { if role != domain.RoleSuperAdmin { // Not a super admin: Only return the entire tree(s) of the tenants they belong to - allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "") + allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "") if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } @@ -343,13 +343,13 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error { } else { // Super Admin case if cursorRaw != "" && h.DB != nil { - tenants, total, nextCursor, err = h.listTenantsByCursor(c.Context(), limit, parentId, cursorRaw) + tenants, total, nextCursor, err = h.listTenantsByCursor(c.Context(), limit, parentId, cursorRaw, "") if err != nil { return errorJSON(c, fiber.StatusBadRequest, "invalid cursor") } offset = 0 } else { - tenants, total, err = h.Service.ListTenants(c.Context(), limit, offset, parentId) + tenants, total, err = h.Service.ListTenants(c.Context(), limit, offset, parentId, "") if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } @@ -382,7 +382,7 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error { }) } -func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, parentID string, cursorRaw string) ([]domain.Tenant, int64, string, error) { +func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, parentID string, cursorRaw string, search string) ([]domain.Tenant, int64, string, error) { cursor, err := pagination.Decode(cursorRaw) if err != nil { return nil, 0, "", err @@ -395,6 +395,12 @@ func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, pare pageQuery = pageQuery.Where("parent_id = ?", parentID) } + if search != "" { + searchTerm := "%" + strings.ToLower(search) + "%" + countQuery = countQuery.Where("LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm, searchTerm) + pageQuery = pageQuery.Where("LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm, searchTerm) + } + var total int64 if err := countQuery.Count(&total).Error; err != nil { return nil, 0, "", err @@ -422,7 +428,7 @@ func (h *TenantHandler) listTenantsByCursor(ctx context.Context, limit int, pare func (h *TenantHandler) ExportTenantsCSV(c *fiber.Ctx) error { parentID := strings.TrimSpace(c.Query("parentId")) - allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "") + allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "") if err != nil { return errorJSON(c, fiber.StatusInternalServerError, err.Error()) } @@ -566,7 +572,7 @@ func (h *TenantHandler) ImportTenantsCSV(c *fiber.Ctx) error { tenantIDBySlug := make(map[string]string) if h.Service != nil { - if tenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, ""); err == nil { + if tenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", ""); err == nil { for _, tenant := range tenants { tenantIDBySlug[strings.ToLower(tenant.Slug)] = tenant.ID } @@ -2336,7 +2342,7 @@ func (h *TenantHandler) GetOrgContext(c *fiber.Ctx) error { return errorJSON(c, fiber.StatusServiceUnavailable, "tenant service is not configured") } - allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "") + allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "") if err != nil { return errorJSON(c, fiber.StatusInternalServerError, err.Error()) } @@ -2410,7 +2416,7 @@ func (h *TenantHandler) loadOrgContextMembers(ctx context.Context, tenantIDs, te if err != nil { return nil, err } - usersByAppointment, _, err := h.UserRepo.List(ctx, 0, 10000, "", "") + usersByAppointment, _, _, err := h.UserRepo.List(ctx, 0, 10000, "", []string{}, "") if err != nil { return nil, err } @@ -2741,7 +2747,7 @@ func (h *TenantHandler) GetPublicOrgChart(c *fiber.Ctx) error { return errorJSON(c, fiber.StatusUnauthorized, err.Error()) } - allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "") + allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "", "") if err != nil { return errorJSON(c, fiber.StatusInternalServerError, err.Error()) } diff --git a/backend/internal/handler/tenant_handler_test.go b/backend/internal/handler/tenant_handler_test.go index cd91f48d..a7f618ad 100644 --- a/backend/internal/handler/tenant_handler_test.go +++ b/backend/internal/handler/tenant_handler_test.go @@ -72,8 +72,8 @@ func (m *MockTenantService) GetTenant(ctx context.Context, id string) (*domain.T return args.Get(0).(*domain.Tenant), args.Error(1) } -func (m *MockTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { - args := m.Called(ctx, limit, offset, parentID) +func (m *MockTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { + args := m.Called(ctx, limit, offset, parentID, search) return args.Get(0).([]domain.Tenant), args.Get(1).(int64), args.Error(2) } @@ -134,14 +134,14 @@ func (m *MockUserRepoForHandler) ListByTenant(ctx context.Context, tenantID stri return nil, nil } -func (m *MockUserRepoForHandler) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { +func (m *MockUserRepoForHandler) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) { for _, call := range m.ExpectedCalls { if call.Method == "List" { - args := m.Called(ctx, offset, limit, search, tenantSlug) - return args.Get(0).([]domain.User), args.Get(1).(int64), args.Error(2) + args := m.Called(ctx, offset, limit, search, tenantIDs, cursor) + return args.Get(0).([]domain.User), args.Get(1).(int64), args.String(2), args.Error(3) } } - return nil, 0, nil + return nil, 0, "", nil } func (m *MockUserRepoForHandler) CountByTenant(ctx context.Context, tenantID string) (int64, error) { @@ -274,7 +274,7 @@ func TestTenantHandler_ListTenantsUsesReadyUserProjectionCountsWithoutKratos(t * tenants := []domain.Tenant{ {ID: "00000000-0000-0000-0000-000000000001", Name: "Saman", Slug: "saman"}, } - mockSvc.On("ListTenants", mock.Anything, 10, 0, "").Return(tenants, int64(1), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10, 0, "", "").Return(tenants, int64(1), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(true, nil).Once() mockProjection.On("CountTenantMembers", mock.Anything, tenants). Return(map[string]int64{"00000000-0000-0000-0000-000000000001": 2}, nil).Once() @@ -313,7 +313,7 @@ func TestTenantHandler_ListTenantsRejectsStatsWhenUserProjectionIsNotReady(t *te tenants := []domain.Tenant{ {ID: "00000000-0000-0000-0000-000000000001", Name: "Saman", Slug: "saman"}, } - mockSvc.On("ListTenants", mock.Anything, 10, 0, "").Return(tenants, int64(1), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10, 0, "", "").Return(tenants, int64(1), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(false, nil).Once() req := httptest.NewRequest("GET", "/tenants?limit=10&offset=0", nil) @@ -346,7 +346,7 @@ func TestTenantHandler_ListTenants(t *testing.T) { } // Mocking for the new allTenants check in ListTenants - mockSvc.On("ListTenants", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tenants, int64(2), nil).Maybe() + mockSvc.On("ListTenants", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tenants, int64(2), nil).Maybe() mockProjection.On("IsReady", mock.Anything).Return(true, nil).Once() mockProjection.On("CountTenantMembers", mock.Anything, tenants). Return(map[string]int64{"t1": 5, "t2": 10}, nil).Once() @@ -396,7 +396,7 @@ func TestTenantHandler_ListTenantsReturnsNextCursorWhenMoreRowsExist(t *testing. {ID: "00000000-0000-0000-0000-000000000001", Name: "Tenant A", Slug: "slug-a", CreatedAt: createdAt.Add(-time.Minute)}, } - mockSvc.On("ListTenants", mock.Anything, 2, 0, "").Return(tenants, int64(3), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 2, 0, "", "").Return(tenants, int64(3), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(true, nil).Once() mockProjection.On("CountTenantMembers", mock.Anything, tenants).Return(map[string]int64{}, nil).Once() @@ -463,7 +463,7 @@ func TestTenantHandler_ListTenantsHidesPrivateSubtreeForUnauthorizedUser(t *test }) app.Get("/tenants", h.ListTenants) - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(true, nil).Once() mockProjection.On("CountTenantMembers", mock.Anything, mock.MatchedBy(func(got []domain.Tenant) bool { return tenantSlugsMatch(got, "hanmac-family", "hanmac", "public-team") @@ -512,7 +512,7 @@ func TestTenantHandler_ListTenantsShowsPrivateSubtreeForManageableTenant(t *test }) app.Get("/tenants", h.ListTenants) - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(true, nil).Once() mockProjection.On("CountTenantMembers", mock.Anything, mock.MatchedBy(func(got []domain.Tenant) bool { return tenantSlugsMatch(got, "hanmac-family", "hanmac", "private-team", "private-child") @@ -704,10 +704,10 @@ func TestTenantHandler_GetOrgContextJSONDefaultsToHanmacFamilyForApiKey(t *testi }, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil) mockUsers.On("FindByTenantIDs", mock.Anything, []string{"group-hanmac-family", "company-hanmac", "dept-platform", "team-sso"}).Return(usersByTenantID, nil) mockUsers.On("FindByCompanyCodes", mock.Anything, []string{"hanmac-family", "hanmac", "platform", "sso"}).Return(usersBySlug, nil) - mockUsers.On("List", mock.Anything, 0, 10000, "", "").Return(usersByList, int64(len(usersByList)), nil) + mockUsers.On("List", mock.Anything, 0, 10000, "", mock.Anything, "").Return(usersByList, int64(len(usersByList)), "", nil) req := httptest.NewRequest(http.MethodGet, "/org-context", nil) resp, err := app.Test(req) @@ -798,7 +798,7 @@ func TestTenantHandler_GetOrgContextJSONIncludesUserIDsOnlyWhenRequested(t *test {ID: "user-1", Email: "user@example.com", Name: "사용자", Phone: "010-1234-5678", Status: domain.UserStatusActive, TenantID: parent("company-hanmac"), CompanyCode: "hanmac", CreatedAt: now, UpdatedAt: now}, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil) mockUsers.On("FindByTenantIDs", mock.Anything, []string{"company-hanmac"}).Return(users, nil) mockUsers.On("FindByCompanyCodes", mock.Anything, []string{"hanmac"}).Return([]domain.User{}, nil) @@ -847,7 +847,7 @@ func TestTenantHandler_GetOrgContextJSONScopesByTenantSlug(t *testing.T) { {ID: "dept-platform", Type: domain.TenantTypeUserGroup, ParentID: parent("company-hanmac"), Name: "플랫폼실", Slug: "platform", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now}, {ID: "company-other", Type: domain.TenantTypeCompany, ParentID: parent("group-hanmac-family"), Name: "다른회사", Slug: "other", Status: domain.TenantStatusActive, CreatedAt: now, UpdatedAt: now}, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil) mockUsers.On("FindByTenantIDs", mock.Anything, []string{"company-hanmac", "dept-platform"}).Return([]domain.User{}, nil) mockUsers.On("FindByCompanyCodes", mock.Anything, []string{"hanmac", "platform"}).Return([]domain.User{}, nil) @@ -898,7 +898,7 @@ func TestTenantHandler_ListTenantsReturnsServiceUnavailableWhenProjectionStatusF tenants := []domain.Tenant{ {ID: "t1", Name: "Tenant A", Slug: "slug-a"}, } - mockSvc.On("ListTenants", mock.Anything, 10, 0, "").Return(tenants, int64(1), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10, 0, "", "").Return(tenants, int64(1), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(false, errors.New("projection state query failed")).Once() req := httptest.NewRequest("GET", "/tenants?limit=10&offset=0", nil) @@ -932,7 +932,7 @@ func TestTenantHandler_ListTenantsUsesProjectionCountsWhenAvailable(t *testing.T {ID: "00000000-0000-0000-0000-000000000001", Name: "Saman", Slug: "saman"}, } - mockSvc.On("ListTenants", mock.Anything, 10, 0, "").Return(tenants, int64(1), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10, 0, "", "").Return(tenants, int64(1), nil).Once() mockProjection.On("IsReady", mock.Anything).Return(true, nil).Once() mockProjection.On("CountTenantMembers", mock.Anything, tenants). Return(map[string]int64{"00000000-0000-0000-0000-000000000001": 2}, nil).Once() @@ -982,7 +982,7 @@ func TestTenantHandler_ExportTenantsCSV(t *testing.T) { }, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(1), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(1), nil) req := httptest.NewRequest("GET", "/tenants/export?includeIds=true", nil) resp, _ := app.Test(req) @@ -1019,7 +1019,7 @@ func TestTenantHandler_ExportTenantsCSV_OmitsIDsAndUsesParentSlug(t *testing.T) }, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(2), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(2), nil) req := httptest.NewRequest("GET", "/tenants/export?includeIds=false", nil) resp, _ := app.Test(req) @@ -1051,7 +1051,7 @@ func TestTenantHandler_ExportTenantsCSV_OrdersByInputOrder(t *testing.T) { {ID: "oldest", Name: "Oldest Tenant", Type: domain.TenantTypeCompany, Slug: "oldest", CreatedAt: oldest}, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil) req := httptest.NewRequest("GET", "/tenants/export?includeIds=true", nil) resp, _ := app.Test(req) @@ -1106,7 +1106,7 @@ func TestTenantHandler_ExportTenantsCSV_FiltersDescendantsByParentIDWithIDs(t *t }, } - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil) + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil) req := httptest.NewRequest("GET", "/tenants/export?includeIds=true&parentId="+parentID, nil) resp, _ := app.Test(req) @@ -1146,7 +1146,7 @@ func TestTenantHandler_ExportTenantsCSV_HidesPrivateSubtreeForUnauthorizedUser(t }) app.Get("/tenants/export", h.ExportTenantsCSV) - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil).Once() req := httptest.NewRequest("GET", "/tenants/export?includeIds=true", nil) resp, _ := app.Test(req) @@ -1175,7 +1175,7 @@ func TestTenantHandler_ImportTenantsCSVCreatesTenant(t *testing.T) { assert.NoError(t, err) assert.NoError(t, writer.Close()) - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return([]domain.Tenant{}, int64(0), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return([]domain.Tenant{}, int64(0), nil).Once() mockSvc.On( "RegisterTenant", mock.Anything, @@ -1219,7 +1219,7 @@ func TestTenantHandler_ImportTenantsCSVResolvesParentSlugToID(t *testing.T) { assert.NoError(t, writer.Close()) parentID := "parent-id" - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return([]domain.Tenant{}, int64(0), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return([]domain.Tenant{}, int64(0), nil).Once() mockSvc.On( "RegisterTenant", mock.Anything, @@ -1276,7 +1276,7 @@ func TestTenantHandler_ImportTenantsCSVDoesNotAssignCreatorAsOrganizationMember( assert.NoError(t, err) assert.NoError(t, writer.Close()) - mockSvc.On("ListTenants", mock.Anything, 10000, 0, "").Return([]domain.Tenant{}, int64(0), nil).Once() + mockSvc.On("ListTenants", mock.Anything, 10000, 0, "", "").Return([]domain.Tenant{}, int64(0), nil).Once() mockSvc.On( "RegisterTenant", mock.Anything, diff --git a/backend/internal/handler/user_handler.go b/backend/internal/handler/user_handler.go index 64157614..94cb8c04 100644 --- a/backend/internal/handler/user_handler.go +++ b/backend/internal/handler/user_handler.go @@ -486,7 +486,7 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error { // Expand manageableSlugs to the entire tenant tree (root + all descendants) if h.TenantService != nil && len(baseTenantIDs) > 0 { - allTenants, _, err := h.TenantService.ListTenants(c.Context(), 10000, 0, "") + allTenants, _, err := h.TenantService.ListTenants(c.Context(), 10000, 0, "", "") if err == nil { parentMap := make(map[string]string) for _, t := range allTenants { @@ -1614,7 +1614,14 @@ func (h *UserHandler) ExportUsersCSV(c *fiber.Ctx) error { } // 1. Fetch Users using Repo for efficiency - users, _, err := h.UserRepo.List(c.Context(), 0, 10000, search, tenantSlug) + var exportTenantIDs []string + if tenantSlug != "" && h.TenantService != nil { + t, err := h.TenantService.GetTenantBySlug(c.Context(), tenantSlug) + if err == nil && t != nil { + exportTenantIDs = []string{t.ID} + } + } + users, _, _, err := h.UserRepo.List(c.Context(), 0, 10000, search, exportTenantIDs, "") if err != nil { return errorJSON(c, fiber.StatusInternalServerError, "failed to fetch users for export") } diff --git a/backend/internal/handler/user_handler_test.go b/backend/internal/handler/user_handler_test.go index a232e5e8..74dc2cd8 100644 --- a/backend/internal/handler/user_handler_test.go +++ b/backend/internal/handler/user_handler_test.go @@ -291,8 +291,8 @@ func (m *MockTenantServiceForUser) ListManageableTenants(ctx context.Context, us return args.Get(0).([]domain.Tenant), args.Error(1) } -func (m *MockTenantServiceForUser) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { - args := m.Called(ctx, limit, offset, parentID) +func (m *MockTenantServiceForUser) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { + args := m.Called(ctx, limit, offset, parentID, search) if args.Get(0) == nil { return nil, args.Get(1).(int64), args.Error(2) } @@ -332,7 +332,7 @@ func TestUserHandler_ExportUsersCSV_UsesTenantSlugAliasAndOmitsRole(t *testing.T createdAt := time.Date(2026, 4, 29, 12, 0, 0, 0, time.UTC) tenantID := "tenant-uuid" - mockRepo.On("List", mock.Anything, 0, 10000, "", "test-tenant"). + mockRepo.On("List", mock.Anything, 0, 10000, "", []string(nil), ""). Return([]domain.User{ { ID: "u-1", @@ -349,7 +349,7 @@ func TestUserHandler_ExportUsersCSV_UsesTenantSlugAliasAndOmitsRole(t *testing.T JobTitle: "플랫폼 운영", CreatedAt: createdAt, }, - }, int64(1), nil).Maybe() + }, int64(1), "", nil).Maybe() req := httptest.NewRequest("GET", "/users/export?tenantSlug=test-tenant&includeIds=true", nil) resp, err := app.Test(req) @@ -380,7 +380,7 @@ func TestUserHandler_ExportUsersCSV_OmitsIDsAndUsesTenantSlug(t *testing.T) { createdAt := time.Date(2026, 4, 29, 12, 0, 0, 0, time.UTC) tenantID := "tenant-uuid" - mockRepo.On("List", mock.Anything, 0, 10000, "", ""). + mockRepo.On("List", mock.Anything, 0, 10000, "", mock.Anything, ""). Return([]domain.User{ { ID: "user-uuid", @@ -395,7 +395,7 @@ func TestUserHandler_ExportUsersCSV_OmitsIDsAndUsesTenantSlug(t *testing.T) { JobTitle: "플랫폼 운영", CreatedAt: createdAt, }, - }, int64(1), nil).Maybe() + }, int64(1), "", nil).Maybe() req := httptest.NewRequest("GET", "/users/export?includeIds=false", nil) resp, err := app.Test(req) @@ -1049,7 +1049,7 @@ func TestUserHandler_BulkCreateUsers_HanmacEmailPolicy(t *testing.T) { Slug: "hanmac", ParentID: &rootID, }, nil).Maybe() - mockTenant.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil).Maybe() + mockTenant.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil).Maybe() mockRepo.On("FindByTenantIDs", mock.Anything, []string{rootID, companyID, "external-id"}).Return([]domain.User{ {Email: "cyhan@hanmaceng.co.kr", CompanyCode: "hanmac", TenantID: &companyID}, {Email: "cyhan1@samaneng.com", CompanyCode: "hanmac", TenantID: &companyID}, @@ -1117,7 +1117,7 @@ func TestUserHandler_BulkCreateUsers_HanmacEmailPolicy(t *testing.T) { mockTenant.On("GetTenantBySlug", mock.Anything, "h-company").Return(&hTenants[1], nil).Maybe() mockTenant.On("GetTenant", mock.Anything, hCompanyID).Return(&hTenants[1], nil).Maybe() - mockTenant.On("ListTenants", mock.Anything, 10000, 0, "").Return(hTenants, int64(len(hTenants)), nil).Maybe() + mockTenant.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(hTenants, int64(len(hTenants)), nil).Maybe() mockRepo.On("FindByTenantIDs", mock.Anything, mock.MatchedBy(func(ids []string) bool { return slices.Contains(ids, hRootID) || slices.Contains(ids, hCompanyID) @@ -1188,7 +1188,7 @@ func TestUserHandler_CreateUser_HanmacEmailPolicyBlocksDuplicateLocalPart(t *tes ID: companyID, Slug: "hanmac", }, nil).Maybe() - mockTenant.On("ListTenants", mock.Anything, 10000, 0, "").Return(tenants, int64(len(tenants)), nil).Maybe() + mockTenant.On("ListTenants", mock.Anything, 10000, 0, "", "").Return(tenants, int64(len(tenants)), nil).Maybe() mockRepo.On("FindByTenantIDs", mock.Anything, []string{rootID, companyID}).Return([]domain.User{ {Email: "han@hanmaceng.co.kr", CompanyCode: "hanmac", TenantID: &companyID}, }, nil).Maybe() @@ -2146,7 +2146,7 @@ func TestUserHandler_CreateUser_UsesAdditionalAppointmentAsPrimaryTenant(t *test ID: tenantID, Slug: "saman", }, nil) - mockTenant.On("ListTenants", mock.Anything, 10000, 0, "").Return([]domain.Tenant{}, int64(0), nil) + mockTenant.On("ListTenants", mock.Anything, 10000, 0, "", "").Return([]domain.Tenant{}, int64(0), nil) mockOry.On("GetPasswordPolicy").Return(&domain.PasswordPolicy{MinLength: 8}, nil) mockOry.On("CreateUser", mock.Anything, mock.Anything).Return("some-id", nil).Maybe() mockKratos.On("GetIdentity", mock.Anything, "some-id").Return(&service.KratosIdentity{ diff --git a/backend/internal/middleware/tenant_middleware_test.go b/backend/internal/middleware/tenant_middleware_test.go index 97c028fb..657832f8 100644 --- a/backend/internal/middleware/tenant_middleware_test.go +++ b/backend/internal/middleware/tenant_middleware_test.go @@ -45,7 +45,7 @@ func (m *MockTenantServiceForMiddleware) GetTenant(ctx context.Context, id strin return nil, nil } -func (m *MockTenantServiceForMiddleware) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { +func (m *MockTenantServiceForMiddleware) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { return nil, 0, nil } @@ -53,6 +53,10 @@ func (m *MockTenantServiceForMiddleware) ListManageableTenants(ctx context.Conte return nil, nil } +func (m *MockTenantServiceForMiddleware) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) { + return nil, nil +} + func (m *MockTenantServiceForMiddleware) IsDomainAllowed(ctx context.Context, domainName string) (bool, error) { return false, nil } @@ -60,8 +64,17 @@ func (m *MockTenantServiceForMiddleware) IsDomainAllowed(ctx context.Context, do func (m *MockTenantServiceForMiddleware) ApproveTenant(ctx context.Context, id string) error { return nil } + +func (m *MockTenantServiceForMiddleware) ProvisionTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) { + return nil, nil +} + func (m *MockTenantServiceForMiddleware) SetKetoService(keto service.KetoService) {} +func (m *MockTenantServiceForMiddleware) DeleteTenantsBulk(ctx context.Context, ids []string) error { + return nil +} + func TestTenantContextMiddleware(t *testing.T) { os.Setenv("USERFRONT_URL", "https://sso.hmac.kr") defer os.Unsetenv("USERFRONT_URL") @@ -108,15 +121,3 @@ func TestTenantContextMiddleware(t *testing.T) { mockSvc.AssertExpectations(t) }) } - -func (m *MockTenantServiceForMiddleware) DeleteTenantsBulk(ctx context.Context, tenantIDs []string) error { - return nil -} - -func (m *MockTenantServiceForMiddleware) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) { - return nil, nil -} - -func (m *MockTenantServiceForMiddleware) ProvisionTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error) { - return nil, nil -} diff --git a/backend/internal/repository/tenant_repository.go b/backend/internal/repository/tenant_repository.go index 1d808a06..5d6a1b2a 100644 --- a/backend/internal/repository/tenant_repository.go +++ b/backend/internal/repository/tenant_repository.go @@ -20,7 +20,7 @@ type TenantRepository interface { FindByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error) AddDomain(ctx context.Context, tenantID string, domainName string, verified bool) error - List(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) + List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error) DeleteBulk(ctx context.Context, ids []string) error } @@ -124,7 +124,7 @@ func (r *tenantRepository) AddDomain(ctx context.Context, tenantID string, domai return r.db.WithContext(ctx).Create(&td).Error } -func (r *tenantRepository) List(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { +func (r *tenantRepository) List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { var tenants []domain.Tenant var total int64 db := r.db.WithContext(ctx).Model(&domain.Tenant{}) @@ -133,6 +133,11 @@ func (r *tenantRepository) List(ctx context.Context, limit, offset int, parentID db = db.Where("parent_id = ?", parentID) } + if search != "" { + searchTerm := "%" + strings.ToLower(search) + "%" + db = db.Where("LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm, searchTerm) + } + if err := db.Count(&total).Error; err != nil { return nil, 0, err } diff --git a/backend/internal/repository/user_repository.go b/backend/internal/repository/user_repository.go index 3a3856b9..5c871d9e 100644 --- a/backend/internal/repository/user_repository.go +++ b/backend/internal/repository/user_repository.go @@ -2,6 +2,7 @@ package repository import ( "baron-sso-backend/internal/domain" + "baron-sso-backend/internal/pagination" "context" "fmt" "strings" @@ -17,7 +18,7 @@ type UserRepository interface { FindByID(ctx context.Context, id string) (*domain.User, error) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error) - List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) + List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) CountByTenant(ctx context.Context, tenantID string) (int64, error) CountByTenantIDs(ctx context.Context, tenantIDs []string) (map[string]int64, error) CountByCompanyCodes(ctx context.Context, codes []string) (map[string]int64, error) @@ -215,14 +216,13 @@ func lowerStrings(arr []string) []string { return res } -func (r *userRepository) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { +func (r *userRepository) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursorRaw string) ([]domain.User, int64, string, error) { var users []domain.User var total int64 db := r.db.WithContext(ctx).Model(&domain.User{}) - if tenantSlug != "" { - db = db.Joins("LEFT JOIN tenants ON users.tenant_id = tenants.id"). - Where("tenants.slug = ?", tenantSlug) + if len(tenantIDs) > 0 { + db = db.Where("tenant_id IN ?", tenantIDs) } if search != "" { @@ -232,14 +232,34 @@ func (r *userRepository) List(ctx context.Context, offset, limit int, search str } if err := db.Count(&total).Error; err != nil { - return nil, 0, err + return nil, 0, "", err } - if err := db.Offset(offset).Limit(limit).Preload("Tenant").Find(&users).Error; err != nil { - return nil, 0, err + if cursorRaw != "" { + cursor, err := pagination.Decode(cursorRaw) + if err != nil { + return nil, 0, "", err + } + db = pagination.ApplyCreatedAtIDCursor(db, cursor, "created_at", "id") + } else { + db = db.Offset(offset) } - return users, total, nil + if err := db.Order("created_at desc, id desc").Limit(limit + 1).Preload("Tenant").Find(&users).Error; err != nil { + return nil, 0, "", err + } + + var items []domain.User + var nextCursor string + if len(users) > limit { + items = users[:limit] + last := items[limit-1] + nextCursor = pagination.Encode(last.CreatedAt, last.ID) + } else { + items = users + } + + return items, total, nextCursor, nil } func (r *userRepository) Delete(ctx context.Context, id string) error { diff --git a/backend/internal/repository/user_repository_test.go b/backend/internal/repository/user_repository_test.go index 49ec166a..679e3845 100644 --- a/backend/internal/repository/user_repository_test.go +++ b/backend/internal/repository/user_repository_test.go @@ -88,7 +88,7 @@ func TestUserRepository(t *testing.T) { _ = repo.Create(ctx, &domain.User{Email: "alice@test.com", Name: "Alice", Role: "user"}) _ = repo.Create(ctx, &domain.User{Email: "bob@test.com", Name: "Bob", Role: "user"}) - users, total, err := repo.List(ctx, 0, 10, "Alice", "") + users, total, _, err := repo.List(ctx, 0, 10, "Alice", []string{}, "") assert.NoError(t, err) assert.True(t, total >= 1) assert.Equal(t, "Alice", users[0].Name) diff --git a/backend/internal/service/tenant_service.go b/backend/internal/service/tenant_service.go index 90b75d42..10415e69 100644 --- a/backend/internal/service/tenant_service.go +++ b/backend/internal/service/tenant_service.go @@ -18,7 +18,7 @@ type TenantService interface { GetTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error) GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error) GetTenant(ctx context.Context, id string) (*domain.Tenant, error) - ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) + ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) ListManageableTenants(ctx context.Context, userID string) ([]domain.Tenant, error) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) IsDomainAllowed(ctx context.Context, domainName string) (bool, error) @@ -314,8 +314,8 @@ func (s *tenantService) GetTenantBySlug(ctx context.Context, slug string) (*doma return s.repo.FindBySlug(ctx, slug) } -func (s *tenantService) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { - return s.repo.List(ctx, limit, offset, parentID) +func (s *tenantService) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { + return s.repo.List(ctx, limit, offset, parentID, search) } func (s *tenantService) IsDomainAllowed(ctx context.Context, domainName string) (bool, error) { diff --git a/backend/internal/service/tenant_service_test.go b/backend/internal/service/tenant_service_test.go index fe9b55da..be19a03a 100644 --- a/backend/internal/service/tenant_service_test.go +++ b/backend/internal/service/tenant_service_test.go @@ -60,9 +60,9 @@ func (m *MockTenantRepoForSvc) AddDomain(ctx context.Context, tenantID string, d return m.Called(ctx, tenantID, domainName, verified).Error(0) } -func (m *MockTenantRepoForSvc) List(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { - args := m.Called(ctx, limit, offset, parentID) - return args.Get(0).([]domain.Tenant), int64(args.Int(1)), args.Error(2) +func (m *MockTenantRepoForSvc) List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { + args := m.Called(ctx, limit, offset, parentID, search) + return args.Get(0).([]domain.Tenant), args.Get(1).(int64), args.Error(2) } func (m *MockTenantRepoForSvc) ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error) { @@ -135,8 +135,8 @@ func (m *MockUserRepoForTenant) ListByTenant(ctx context.Context, tenantID strin return nil, nil } -func (m *MockUserRepoForTenant) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { - return nil, 0, nil +func (m *MockUserRepoForTenant) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) { + return nil, 0, "", nil } func (m *MockUserRepoForTenant) CountByTenant(ctx context.Context, tenantID string) (int64, error) { @@ -335,9 +335,9 @@ func TestTenantService_ListTenants(t *testing.T) { ctx := context.Background() tenants := []domain.Tenant{{ID: "t1", Name: "Tenant 1"}} - mockRepo.On("List", ctx, 10, 0, "").Return(tenants, 1, nil) + mockRepo.On("List", ctx, 10, 0, "", "").Return(tenants, int64(1), nil) - result, total, err := svc.ListTenants(ctx, 10, 0, "") + result, total, err := svc.ListTenants(ctx, 10, 0, "", "") assert.NoError(t, err) assert.Equal(t, int64(1), total) assert.Equal(t, tenants, result) diff --git a/backend/internal/service/user_group_service_test.go b/backend/internal/service/user_group_service_test.go index 3b8b3d80..c61b2cf9 100644 --- a/backend/internal/service/user_group_service_test.go +++ b/backend/internal/service/user_group_service_test.go @@ -84,8 +84,8 @@ func (m *MockUserRepository) ListByTenant(ctx context.Context, tenantID string) return nil, nil } -func (m *MockUserRepository) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { - return nil, 0, nil +func (m *MockUserRepository) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) { + return nil, 0, "", nil } func (m *MockUserRepository) CountByTenant(ctx context.Context, tenantID string) (int64, error) { @@ -200,7 +200,7 @@ func (m *MockTenantRepository) FindByDomain(ctx context.Context, domainName stri return nil, nil } -func (m *MockTenantRepository) List(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { +func (m *MockTenantRepository) List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { return nil, 0, nil } diff --git a/backend/internal/service/worksmobile_sync_service.go b/backend/internal/service/worksmobile_sync_service.go index 06f39f3f..ff890c78 100644 --- a/backend/internal/service/worksmobile_sync_service.go +++ b/backend/internal/service/worksmobile_sync_service.go @@ -938,7 +938,7 @@ func (s *worksmobileSyncService) hanmacRoot(ctx context.Context, tenantID string } func (s *worksmobileSyncService) hanmacSubtree(ctx context.Context, rootID string) ([]domain.Tenant, error) { - all, _, err := s.tenantService.ListTenants(ctx, 10000, 0, "") + all, _, err := s.tenantService.ListTenants(ctx, 10000, 0, "", "") if err != nil { return nil, err } diff --git a/backend/internal/service/worksmobile_sync_service_test.go b/backend/internal/service/worksmobile_sync_service_test.go index 3ec0aaff..94ce46e5 100644 --- a/backend/internal/service/worksmobile_sync_service_test.go +++ b/backend/internal/service/worksmobile_sync_service_test.go @@ -1978,7 +1978,7 @@ func (f *fakeWorksmobileTenantService) GetTenant(ctx context.Context, id string) return &tenant, nil } -func (f *fakeWorksmobileTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) { +func (f *fakeWorksmobileTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) { return f.list, int64(len(f.list)), nil } @@ -2033,8 +2033,8 @@ func (f *fakeWorksmobileUserRepo) ListByTenant(ctx context.Context, tenantID str return nil, nil } -func (f *fakeWorksmobileUserRepo) List(ctx context.Context, offset, limit int, search string, tenantSlug string) ([]domain.User, int64, error) { - return nil, 0, nil +func (r *fakeWorksmobileUserRepo) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) { + return nil, 0, "", nil } func (f *fakeWorksmobileUserRepo) CountByTenant(ctx context.Context, tenantID string) (int64, error) { diff --git a/common/core/components/audit/AuditLogTable.tsx b/common/core/components/audit/AuditLogTable.tsx index a525f333..30d94c0c 100644 --- a/common/core/components/audit/AuditLogTable.tsx +++ b/common/core/components/audit/AuditLogTable.tsx @@ -1,9 +1,7 @@ import { ChevronDown, ChevronUp, Copy } from "lucide-react"; import * as React from "react"; -import { - type CommonBadgeVariant, - getCommonBadgeClasses, -} from "../../../ui/badge"; +import { getCommonBadgeClasses } from "../../../ui/badge"; +import type { CommonBadgeVariant } from "../../../ui/badge"; import { getCommonButtonClasses } from "../../../ui/button"; import { commonStickyTableHeaderClass, @@ -48,7 +46,20 @@ function cx(...classNames: Array) { } function statusVariant(status: string): CommonBadgeVariant { - return status === "success" || status === "ok" ? "success" : "warning"; + switch (status.toLowerCase()) { + case "success": + case "ok": + return "success"; + case "failure": + case "error": + case "blocked": + return "destructive"; + case "pending": + case "warning": + return "warning"; + default: + return "default"; + } } export function AuditLogTable({ @@ -73,356 +84,324 @@ export function AuditLogTable({ return (
-
+
- - - -
+ + + + {t("ui.common.audit.table.time", "Time")} - - - - - - - - - {loading && logs.length === 0 ? ( - - - - ) : logs.length === 0 ? ( - - - - ) : ( - logs.map((row, index) => { - const details = parseAuditDetails(row.details); - const actorLabel = resolveAuditActor(row, details); - const actionLabel = resolveAuditAction(row, details); - const targetLabel = resolveAuditTarget(details); - const rowKey = `${row.event_id}-${row.timestamp}-${index}`; - const expanded = Boolean(expandedRows[rowKey]); - const { date, time } = formatAuditDateParts(row.timestamp); - return ( - - - - - - - - - - {expanded ? ( - - - - ) : null} - - ); - }) + + )} - -
+ + {t("ui.common.audit.table.user_id", "User ID")} - + + {t("ui.common.audit.table.action", "Action")} - + + {t("ui.common.audit.table.client_id", "Client ID")} - + + {t("ui.common.audit.table.status", "Status")} - -
+ + + + + {logs.map((log, index) => { + const details = parseAuditDetails(log.details); + const actorLabel = resolveAuditActor(log, details); + const actionLabel = resolveAuditAction(log, details); + const targetLabel = resolveAuditTarget(details); + const rowKey = `${log.event_id}-${log.timestamp}-${index}`; + const expanded = Boolean(expandedRows[rowKey]); + const { date, time } = formatAuditDateParts(log.timestamp); + + return ( + + + +
+
{date}
+
{time}
+
+
+ +
+ + {actorLabel} + + {actorLabel !== "-" ? ( + + ) : null} +
+
+ +
+ {actionLabel} +
+
+ +
+ {targetLabel} + {targetLabel !== "-" ? ( + + ) : null} +
+
+ + + {log.status} + + + + + +
+ {expanded && ( + + +
+
+
+ {t("ui.common.audit.details.request", "Request")} +
+
+ {t( + "ui.common.audit.details.request_id", + "Request ID · {{value}}", + { value: formatAuditValue(details.request_id) }, + )} +
+
+ {t( + "ui.common.audit.details.event_id", + "Event ID · {{value}}", + { value: formatAuditValue(log.event_id) }, + )} +
+
+ {t("ui.common.audit.details.ip", "IP · {{value}}", { + value: formatAuditValue(log.ip_address), + })} +
+
+ {t( + "ui.common.audit.details.method", + "Method · {{value}}", + { value: formatAuditValue(details.method) }, + )} +
+
+ {t( + "ui.common.audit.details.path", + "Path · {{value}}", + { value: formatAuditValue(details.path) }, + )} +
+
+ {t( + "ui.common.audit.details.latency", + "Latency · {{value}}", + { + value: + details.latency_ms !== undefined + ? `${details.latency_ms}ms` + : "-", + }, + )} +
+
+
+
+ {t("ui.common.audit.details.actor", "Actor")} +
+
+ {t( + "ui.common.audit.details.actor_id", + "User ID · {{value}}", + { value: actorLabel }, + )} +
+
+ {t( + "ui.common.audit.details.tenant", + "Tenant · {{value}}", + { value: formatAuditValue(details.tenant_id) }, + )} +
+
+ {t( + "ui.common.audit.details.device", + "Device · {{value}}", + { value: formatAuditValue(log.device_id) }, + )} +
+
+ {t( + "ui.common.audit.details.target", + "Client ID · {{value}}", + { value: targetLabel }, + )} +
+
+
+
+ {t("ui.common.audit.details.result", "Result")} +
+
+ {t( + "ui.common.audit.details.error", + "Error · {{value}}", + { value: formatAuditValue(details.error) }, + )} +
+
+ {t( + "ui.common.audit.details.before", + "Before · {{value}}", + { value: formatAuditValue(details.before) }, + )} +
+
+ {t( + "ui.common.audit.details.after", + "After · {{value}}", + { value: formatAuditValue(details.after) }, + )} +
+
+
+
+
)} - > - {t("msg.common.audit.loading", "Loading audit logs...")} -
+ ); + })} + {logs.length === 0 && !loading && ( + + {t("msg.common.audit.empty", "No audit logs found.")} -
-
-
{date}
-
{time}
-
-
-
- - {actorLabel} - - {actorLabel !== "-" ? ( - - ) : null} -
-
-
- {actionLabel} -
-
-
- {targetLabel} - {targetLabel !== "-" ? ( - - ) : null} -
-
- - {row.status} - - - -
-
-
-
- {t( - "ui.common.audit.details.request", - "Request", - )} -
-
- {t( - "ui.common.audit.details.request_id", - "Request ID · {{value}}", - { - value: formatAuditValue( - details.request_id, - ), - }, - )} -
-
- {t( - "ui.common.audit.details.event_id", - "Event ID · {{value}}", - { - value: formatAuditValue(row.event_id), - }, - )} -
-
- {t( - "ui.common.audit.details.ip", - "IP · {{value}}", - { - value: formatAuditValue(row.ip_address), - }, - )} -
-
- {t( - "ui.common.audit.details.method", - "Method · {{value}}", - { - value: formatAuditValue(details.method), - }, - )} -
-
- {t( - "ui.common.audit.details.path", - "Path · {{value}}", - { - value: formatAuditValue(details.path), - }, - )} -
-
- {t( - "ui.common.audit.details.latency", - "Latency · {{value}}", - { - value: - details.latency_ms !== undefined - ? `${details.latency_ms}ms` - : "-", - }, - )} -
-
-
-
- {t("ui.common.audit.details.actor", "Actor")} -
-
- {t( - "ui.common.audit.details.actor_id", - "User ID · {{value}}", - { value: actorLabel }, - )} -
-
- {t( - "ui.common.audit.details.tenant", - "Tenant · {{value}}", - { - value: formatAuditValue( - details.tenant_id, - ), - }, - )} -
-
- {t( - "ui.common.audit.details.device", - "Device · {{value}}", - { - value: formatAuditValue(row.device_id), - }, - )} -
-
- {t( - "ui.common.audit.details.target", - "Client ID · {{value}}", - { value: targetLabel }, - )} -
-
-
-
- {t( - "ui.common.audit.details.result", - "Result", - )} -
-
- {t( - "ui.common.audit.details.error", - "Error · {{value}}", - { - value: formatAuditValue(details.error), - }, - )} -
-
- {t( - "ui.common.audit.details.before", - "Before · {{value}}", - { - value: formatAuditValue(details.before), - }, - )} -
-
- {t( - "ui.common.audit.details.after", - "After · {{value}}", - { - value: formatAuditValue(details.after), - }, - )} -
-
-
-
+ +
-
+
{hasNextPage ? ( - - ) : ( +
+ {isFetchingNextPage && ( + + {t("msg.common.loading", "Loading more...")} + + )} + +
+ ) : logs.length > 0 ? ( {t("msg.common.audit.end", "End of audit feed")} - )} + ) : null}
); } + +// Internal table components for cleaner implementation +function Table({ className, children, style }: { className?: string, children: React.ReactNode, style?: React.CSSProperties }) { + return {children}
; +} + +function TableHeader({ className, children }: { className?: string, children: React.ReactNode }) { + return {children}; +} + +function TableBody({ className, children }: { className?: string, children: React.ReactNode }) { + return {children}; +} + +function TableRow({ className, children }: { className?: string, children: React.ReactNode }) { + return {children}; +} + +function TableHead({ className, children }: { className?: string, children?: React.ReactNode }) { + return {children}; +} + +function TableCell({ className, children, colSpan }: { className?: string, children: React.ReactNode, colSpan?: number }) { + return {children}; +} diff --git a/common/locales/en.toml b/common/locales/en.toml index 8b6d9b81..033d3b9b 100644 --- a/common/locales/en.toml +++ b/common/locales/en.toml @@ -1,4 +1,5 @@ [msg.common] +loading_more = "Loading more logs..." copied = "Copied." error = "Error" forbidden = "Access denied." @@ -141,6 +142,7 @@ target = "Client ID · {{value}}" title = "Audit registry" [ui.common.audit.table] +no_logs = "No logs to display." action = "Action" actor = "User ID" client_id = "Client ID" diff --git a/common/locales/ko.toml b/common/locales/ko.toml index 57ba4cdb..df1181b5 100644 --- a/common/locales/ko.toml +++ b/common/locales/ko.toml @@ -1,4 +1,5 @@ [msg.common] +loading_more = "추가 로그를 불러오는 중..." copied = "복사되었습니다." error = "오류가 발생했습니다." forbidden = "접근 권한이 없습니다." @@ -141,7 +142,8 @@ target = "클라이언트 ID · {{value}}" title = "감사 로그 레지스트리" [ui.common.audit.table] -action = "액션" +no_logs = "표시할 로그가 없습니다." +action = "작업" actor = "사용자 ID" client_id = "클라이언트 ID" user_id = "사용자 ID" diff --git a/common/locales/template.toml b/common/locales/template.toml index 9e536ab3..2c805c39 100644 --- a/common/locales/template.toml +++ b/common/locales/template.toml @@ -1,4 +1,5 @@ [msg.common] +loading_more = "" copied = "" error = "" forbidden = "" @@ -141,6 +142,7 @@ target = "" title = "" [ui.common.audit.table] +no_logs = "" action = "" actor = "" client_id = "" diff --git a/common/pnpm-workspace.yaml b/common/pnpm-workspace.yaml deleted file mode 100644 index afc5f58a..00000000 --- a/common/pnpm-workspace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -packages: - - "../adminfront" - - "../devfront" - - "../orgfront" -allowBuilds: - '@biomejs/biome': false diff --git a/package.json b/package.json new file mode 100644 index 00000000..e4b774cf --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "baron-sso-root", + "private": true, + "pnpm": { + "overrides": { + "@types/node": "24.12.4", + "undici": "7.26.0", + "electron-to-chromium": "1.5.360", + "@csstools/css-syntax-patches-for-csstree": "1.1.4" + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..bc82c5db --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4239 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + '@types/node': 24.12.4 + undici: 7.26.0 + electron-to-chromium: 1.5.360 + '@csstools/css-syntax-patches-for-csstree': 1.1.4 + +importers: + + .: {} + + adminfront: + dependencies: + '@radix-ui/react-avatar': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/react-query': + specifier: ^5.100.10 + version: 5.101.0(react@19.2.7) + '@tanstack/react-query-devtools': + specifier: ^5.100.10 + version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7) + '@tanstack/react-virtual': + specifier: ^3.13.24 + version: 3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + axios: + specifier: ^1.16.1 + version: 1.17.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.14.0 + version: 1.17.0(react@19.2.7) + oidc-client-ts: + specifier: ^3.5.0 + version: 3.5.0 + react: + specifier: ^19.2.6 + version: 19.2.7 + react-dom: + specifier: ^19.2.6 + version: 19.2.7(react@19.2.7) + react-hook-form: + specifier: ^7.75.0 + version: 7.77.0(react@19.2.7) + react-oidc-context: + specifier: ^3.3.1 + version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.7) + react-router-dom: + specifier: ^7.15.0 + version: 7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@biomejs/biome': + specifier: 2.4.16 + version: 2.4.16 + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: 24.12.4 + version: 24.12.4 + '@types/react': + specifier: ^19.2.14 + version: 19.2.16 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.16) + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.2(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + '@vitest/coverage-v8': + specifier: 4.1.6 + version: 4.1.6(vitest@4.1.6) + autoprefixer: + specifier: ^10.5.0 + version: 10.5.0(postcss@8.5.15) + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + playwright: + specifier: 1.60.0 + version: 1.60.0 + postcss: + specifier: ^8.5.14 + version: 8.5.15 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^8.0.14 + version: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + vitest: + specifier: ^4.1.6 + version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + + common: + dependencies: + '@radix-ui/react-avatar': + specifier: ^1.1.4 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-scroll-area': + specifier: ^1.1.2 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': + specifier: ^1.1.2 + version: 1.2.4(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-switch': + specifier: ^1.1.2 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/react-query': + specifier: ^5.66.8 + version: 5.101.0(react@19.2.7) + '@tanstack/react-query-devtools': + specifier: ^5.66.8 + version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7) + axios: + specifier: ^1.7.9 + version: 1.17.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.563.0 + version: 0.563.0(react@19.2.7) + oidc-client-ts: + specifier: ^3.4.1 + version: 3.5.0 + react: + specifier: ^19.2.0 + version: 19.2.7 + react-dom: + specifier: ^19.2.0 + version: 19.2.7(react@19.2.7) + react-hook-form: + specifier: ^7.71.1 + version: 7.77.0(react@19.2.7) + react-oidc-context: + specifier: ^3.3.0 + version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.7) + react-router-dom: + specifier: ^7.15.1 + version: 7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tailwind-merge: + specifier: ^3.4.0 + version: 3.6.0 + zod: + specifier: ^3.24.1 + version: 3.25.76 + devDependencies: + '@biomejs/biome': + specifier: 2.4.16 + version: 2.4.16 + '@playwright/test': + specifier: ^1.58.0 + version: 1.60.0 + '@types/node': + specifier: 24.12.4 + version: 24.12.4 + '@types/react': + specifier: ^19.2.5 + version: 19.2.16 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.16) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.2(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + autoprefixer: + specifier: ^10.4.23 + version: 10.5.0(postcss@8.5.15) + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + postcss: + specifier: ^8.5.6 + version: 8.5.15 + tailwindcss: + specifier: ^3.4.14 + version: 3.4.19 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: ^8.0.3 + version: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + vitest: + specifier: ^4.1.5 + version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + + devfront: + dependencies: + '@radix-ui/react-avatar': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/react-query': + specifier: ^5.100.10 + version: 5.101.0(react@19.2.7) + '@tanstack/react-query-devtools': + specifier: ^5.100.10 + version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7) + axios: + specifier: ^1.16.1 + version: 1.17.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.14.0 + version: 1.17.0(react@19.2.7) + oidc-client-ts: + specifier: ^3.5.0 + version: 3.5.0 + react: + specifier: ^19.2.6 + version: 19.2.7 + react-dom: + specifier: ^19.2.6 + version: 19.2.7(react@19.2.7) + react-hook-form: + specifier: ^7.75.0 + version: 7.77.0(react@19.2.7) + react-oidc-context: + specifier: ^3.3.1 + version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.7) + react-router-dom: + specifier: ^7.15.0 + version: 7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@biomejs/biome': + specifier: 2.4.16 + version: 2.4.16 + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 + '@types/node': + specifier: 24.12.4 + version: 24.12.4 + '@types/react': + specifier: ^19.2.14 + version: 19.2.16 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.16) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.2(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + '@vitest/coverage-v8': + specifier: 4.1.6 + version: 4.1.6(vitest@4.1.6) + autoprefixer: + specifier: ^10.5.0 + version: 10.5.0(postcss@8.5.15) + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + postcss: + specifier: ^8.5.14 + version: 8.5.15 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^8.0.14 + version: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + vitest: + specifier: ^4.1.6 + version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + + orgfront: + dependencies: + '@radix-ui/react-avatar': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/react-query': + specifier: ^5.100.10 + version: 5.101.0(react@19.2.7) + '@tanstack/react-query-devtools': + specifier: ^5.100.10 + version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7) + '@xyflow/react': + specifier: ^12.10.2 + version: 12.11.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + axios: + specifier: ^1.16.1 + version: 1.17.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.14.0 + version: 1.17.0(react@19.2.7) + oidc-client-ts: + specifier: ^3.5.0 + version: 3.5.0 + react: + specifier: ^19.2.6 + version: 19.2.7 + react-dom: + specifier: ^19.2.6 + version: 19.2.7(react@19.2.7) + react-hook-form: + specifier: ^7.75.0 + version: 7.77.0(react@19.2.7) + react-oidc-context: + specifier: ^3.3.1 + version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.7) + react-router-dom: + specifier: ^7.15.0 + version: 7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@biomejs/biome': + specifier: 2.4.16 + version: 2.4.16 + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 + '@types/node': + specifier: 24.12.4 + version: 24.12.4 + '@types/react': + specifier: ^19.2.14 + version: 19.2.16 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.16) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.2(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + '@vitest/coverage-v8': + specifier: 4.1.6 + version: 4.1.6(vitest@4.1.6) + autoprefixer: + specifier: ^10.5.0 + version: 10.5.0(postcss@8.5.15) + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + postcss: + specifier: ^8.5.14 + version: 8.5.15 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^8.0.14 + version: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + vitest: + specifier: 4.1.6 + version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + +packages: + + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@biomejs/biome@2.4.16': + resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.16': + resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.16': + resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.16': + resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.4.16': + resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.4.16': + resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.4.16': + resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.4.16': + resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.16': + resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.1': + resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4': + resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.11': + resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tanstack/query-core@5.101.0': + resolution: {integrity: sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==} + + '@tanstack/query-devtools@5.101.0': + resolution: {integrity: sha512-MVqw17k08RQtGGLEL654+dX/btbX9p/8WjkznO//zusLTMaObxi3Q+MoFwGVkC9K3tqjn8qrrNhJevXx4fJTeQ==} + + '@tanstack/react-query-devtools@5.101.0': + resolution: {integrity: sha512-cpZA0+WqKXwrwMfiWZEGGF6QrIWVQFbhBtxqDF5sQsAfrFf47HIE6fiPbQU3wyAUEN2+7UNqLCQe7oG6m3f93w==} + peerDependencies: + '@tanstack/react-query': ^5.101.0 + react: ^18 || ^19 + + '@tanstack/react-query@5.101.0': + resolution: {integrity: sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-virtual@3.14.2': + resolution: {integrity: sha512-IpWnmCLvuymRfeeLNVXIzNEYBFLpd3drVIS91sqV78VTZFyldlChkOocZRCPp1B+Wnk09bcLNme8WaMU/9/9bQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.17.0': + resolution: {integrity: sha512-gOxY/hFkPh/XQYhnThBHzkbkX3Ed+z/iushyz+R+JAr213aXxUDgQoTgTdrDpBSRsjFM73P/KfUyWmaF9WHMkQ==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/history@4.7.11': + resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} + + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react-router-dom@5.3.3': + resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + + '@types/react-router@5.1.20': + resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + + '@types/react@19.2.16': + resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==} + + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.6': + resolution: {integrity: sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==} + peerDependencies: + '@vitest/browser': 4.1.6 + vitest: 4.1.6 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.6': + resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} + + '@vitest/mocker@4.1.6': + resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.6': + resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} + + '@vitest/runner@4.1.6': + resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==} + + '@vitest/snapshot@4.1.6': + resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} + + '@vitest/spy@4.1.6': + resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} + + '@vitest/utils@4.1.6': + resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + + '@xyflow/react@12.11.0': + resolution: {integrity: sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA==} + peerDependencies: + '@types/react': '>=17' + '@types/react-dom': '>=17' + react: '>=17' + react-dom: '>=17' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@xyflow/system@0.0.77': + resolution: {integrity: sha512-qCDCMCQAAgUu8yHnhloHG9F5mwPX5E+Wl8McpYIOPSSXfzFJJoZcwOcsDiAjitVKIg2de1WmJbCHfpcvxprsgg==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.3: + resolution: {integrity: sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.17.0: + resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==} + + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssstyle@6.2.0: + resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.360: + resolution: {integrity: sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lucide-react@0.563.0: + resolution: {integrity: sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lucide-react@1.17.0: + resolution: {integrity: sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + oidc-client-ts@3.5.0: + resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==} + engines: {node: '>=18'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-hook-form@7.77.0: + resolution: {integrity: sha512-Sslh9YDYc0GDlWT/lxasnIduNo4v3yyvqRGvmGKUre5AFjDs/HV9/OafHGD8d+sB2yoL4UIL9L8X9i0WlZZebg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-oidc-context@3.3.1: + resolution: {integrity: sha512-/Azvm9W4DhhOtSDBE73kFInh1b6zZRRfILKbgmk2syExMF0PCYJOn/dGdOOi2BFX8x0rCeUe45NXHU+/+xDcrQ==} + engines: {node: '>=18'} + peerDependencies: + oidc-client-ts: ^3.1.0 + react: '>=16.14.0' + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router-dom@7.16.0: + resolution: {integrity: sha512-kMUAbimWB5FVbF4Bce4bJsiKJWLIUHq/mEG8+CFDnCSgltptBiG5nguducmsJeGKytlCvQud9Qhzpn49iduTlA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.16.0: + resolution: {integrity: sha512-wArC8lVyJb3+jM9OpDyW6hLCizACWkvQR/sSGqSs+o5uEXEtGlqdZ4v8hENR3Jad6i+LRkK93q/+bQAcvl6V1A==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.2: + resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} + + tldts@7.4.2: + resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@7.26.0: + resolution: {integrity: sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==} + engines: {node: '>=20.18.1'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': 24.12.4 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.6: + resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': 24.12.4 + '@vitest/browser-playwright': 4.1.6 + '@vitest/browser-preview': 4.1.6 + '@vitest/browser-webdriverio': 4.1.6 + '@vitest/coverage-istanbul': 4.1.6 + '@vitest/coverage-v8': 4.1.6 + '@vitest/ui': 4.1.6 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@acemir/cssom@0.9.31': {} + + '@adobe/css-tools@4.5.0': {} + + '@alloc/quick-lru@5.2.0': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@biomejs/biome@2.4.16': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.16 + '@biomejs/cli-darwin-x64': 2.4.16 + '@biomejs/cli-linux-arm64': 2.4.16 + '@biomejs/cli-linux-arm64-musl': 2.4.16 + '@biomejs/cli-linux-x64': 2.4.16 + '@biomejs/cli-linux-x64-musl': 2.4.16 + '@biomejs/cli-win32-arm64': 2.4.16 + '@biomejs/cli-win32-x64': 2.4.16 + + '@biomejs/cli-darwin-arm64@2.4.16': + optional: true + + '@biomejs/cli-darwin-x64@2.4.16': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.16': + optional: true + + '@biomejs/cli-linux-arm64@2.4.16': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.16': + optional: true + + '@biomejs/cli-linux-x64@2.4.16': + optional: true + + '@biomejs/cli-win32-arm64@2.4.16': + optional: true + + '@biomejs/cli-win32-x64@2.4.16': + optional: true + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@exodus/bytes@1.15.1': {} + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@floating-ui/utils@0.2.11': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-project/types@0.133.0': {} + + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-context@1.1.3(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.16)(react@19.2.7) + aria-hidden: 1.2.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.16)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + aria-hidden: 1.2.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.16)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/rect': 1.1.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + aria-hidden: 1.2.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.16)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.16)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + use-sync-external-store: 1.6.0(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.16)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.16)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.16 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tanstack/query-core@5.101.0': {} + + '@tanstack/query-devtools@5.101.0': {} + + '@tanstack/react-query-devtools@5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7)': + dependencies: + '@tanstack/query-devtools': 5.101.0 + '@tanstack/react-query': 5.101.0(react@19.2.7) + react: 19.2.7 + + '@tanstack/react-query@5.101.0(react@19.2.7)': + dependencies: + '@tanstack/query-core': 5.101.0 + react: 19.2.7 + + '@tanstack/react-virtual@3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@tanstack/virtual-core': 3.17.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@tanstack/virtual-core@3.17.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/history@4.7.11': {} + + '@types/node@24.12.4': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.16)': + dependencies: + '@types/react': 19.2.16 + + '@types/react-router-dom@5.3.3': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.2.16 + '@types/react-router': 5.1.20 + + '@types/react-router@5.1.20': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.2.16 + + '@types/react@19.2.16': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + + '@vitest/coverage-v8@4.1.6(vitest@4.1.6)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.6 + ast-v8-to-istanbul: 1.0.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + + '@vitest/expect@4.1.6': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.6(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7))': + dependencies: + '@vitest/spy': 4.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + + '@vitest/pretty-format@4.1.6': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.6': + dependencies: + '@vitest/utils': 4.1.6 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + '@vitest/utils': 4.1.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.6': {} + + '@vitest/utils@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@xyflow/react@12.11.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@xyflow/system': 0.0.77 + classcat: 5.0.5 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + zustand: 4.5.7(@types/react@19.2.16)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + transitivePeerDependencies: + - immer + + '@xyflow/system@0.0.77': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.3: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + asynckit@0.4.0: {} + + autoprefixer@10.5.0(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001793 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + + axios@1.17.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.5 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + baseline-browser-mapping@2.10.33: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.360 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001793: {} + + chai@6.2.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + classcat@5.0.5: {} + + clsx@2.1.1: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + cssstyle@6.2.0: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.1 + + csstype@3.2.3: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.360: {} + + entities@8.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.1.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + follow-redirects@1.16.0: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fraction.js@5.3.4: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + indent-string@4.0.0: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jiti@1.21.7: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.2 + '@exodus/bytes': 1.15.1 + cssstyle: 6.2.0 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.26.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + + jwt-decode@4.0.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lru-cache@11.5.1: {} + + lucide-react@0.563.0(react@19.2.7): + dependencies: + react: 19.2.7 + + lucide-react@1.17.0(react@19.2.7): + dependencies: + react: 19.2.7 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.1 + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + min-indent@1.0.1: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.12: {} + + node-releases@2.0.47: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + obug@2.1.1: {} + + oidc-client-ts@3.5.0: + dependencies: + jwt-decode: 4.0.0 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-parse@1.0.7: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + postcss-import@15.1.0(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.15): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.15 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.15 + + postcss-nested@6.2.0(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + proxy-from-env@2.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-hook-form@7.77.0(react@19.2.7): + dependencies: + react: 19.2.7 + + react-is@17.0.2: {} + + react-oidc-context@3.3.1(oidc-client-ts@3.5.0)(react@19.2.7): + dependencies: + oidc-client-ts: 3.5.0 + react: 19.2.7 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.16)(react@19.2.7): + dependencies: + react: 19.2.7 + react-style-singleton: 2.2.3(@types/react@19.2.16)(react@19.2.7) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.16 + + react-remove-scroll@2.7.2(@types/react@19.2.16)(react@19.2.7): + dependencies: + react: 19.2.7 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.16)(react@19.2.7) + react-style-singleton: 2.2.3(@types/react@19.2.16)(react@19.2.7) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.16)(react@19.2.7) + use-sidecar: 1.1.3(@types/react@19.2.16)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + + react-router-dom@7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + + react-router@7.16.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + cookie: 1.1.1 + react: 19.2.7 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + + react-style-singleton@2.2.3(@types/react@19.2.16)(react@19.2.7): + dependencies: + get-nonce: 1.0.1 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.16 + + react@19.2.7: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-from-string@2.0.2: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@7.8.1: {} + + set-cookie-parser@2.7.2: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tailwind-merge@3.6.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.15 + postcss-import: 15.1.0(postcss@8.5.15) + postcss-js: 4.1.0(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15) + postcss-nested: 6.2.0(postcss@8.5.15) + postcss-selector-parser: 6.1.2 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tldts-core@7.4.2: {} + + tldts@7.4.2: + dependencies: + tldts-core: 7.4.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.2 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + typescript@6.0.3: {} + + undici-types@7.16.0: {} + + undici@7.26.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@19.2.16)(react@19.2.7): + dependencies: + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.16 + + use-sidecar@1.1.3(@types/react@19.2.16)(react@19.2.7): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.16 + + use-sync-external-store@1.6.0(react@19.2.7): + dependencies: + react: 19.2.7 + + util-deprecate@1.0.2: {} + + vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.12.4 + fsevents: 2.3.3 + jiti: 1.21.7 + + vitest@4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)): + dependencies: + '@vitest/expect': 4.1.6 + '@vitest/mocker': 4.1.6(vite@8.0.16(@types/node@24.12.4)(jiti@1.21.7)) + '@vitest/pretty-format': 4.1.6 + '@vitest/runner': 4.1.6 + '@vitest/snapshot': 4.1.6 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(@types/node@24.12.4)(jiti@1.21.7) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.4 + '@vitest/coverage-v8': 4.1.6(vitest@4.1.6) + jsdom: 28.1.0 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + zod@3.25.76: {} + + zod@4.4.3: {} + + zustand@4.5.7(@types/react@19.2.16)(react@19.2.7): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.16 + react: 19.2.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..44657a32 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +packages: + - "adminfront" + - "devfront" + - "orgfront" + - "common" +allowBuilds: + '@biomejs/biome': false diff --git a/tenants_2605.csv b/tenants_2605.csv deleted file mode 100644 index 191dbecf..00000000 --- a/tenants_2605.csv +++ /dev/null @@ -1,189 +0,0 @@ -tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type -cd1ebc22-4b5e-4242-bb87-eb88db32286c,업무,ORGANIZATION,a16f49c4-6828-4fde-a164-43099c4560c4,planning,operations,,,public,팀 -0e13d342-d3cf-46b5-8096-4e7883b79b01,서산시자원회수시설,ORGANIZATION,d7379c32-0b79-482e-9c4d-d83ad425c3fc,halla-operation-sites,ops-seosan-recovery,,,public, -41118f16-7f5c-4209-bd83-183822bc00ed,안성제4차산업단지폐수처리,ORGANIZATION,d7379c32-0b79-482e-9c4d-d83ad425c3fc,halla-operation-sites,ops-anseong-wwtp,,,public, -ad6f20e9-7928-4322-932c-7c3cb2a313cb,온산바이오,ORGANIZATION,d7379c32-0b79-482e-9c4d-d83ad425c3fc,halla-operation-sites,ops-onsan-bio,,,public, -03d8cf87-4b40-4784-a6cf-fcc11371f40f,울산민자소각,ORGANIZATION,d7379c32-0b79-482e-9c4d-d83ad425c3fc,halla-operation-sites,ops-ulsan-incineration,,,public, -d7379c32-0b79-482e-9c4d-d83ad425c3fc,운영사업소,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-operation-sites,,,public, -551991d8-1f74-4ad0-a0c5-bc5a11968398,부산항 신항,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-busan-new-port,,,public, -e77b4bf1-a126-4b4e-a18a-8d905e958873,수도권광역급행철도B 제4공구,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-gtx-b-4,,,public, -3b5151f6-1a01-484a-bfb7-2e60d2aa0b49,경산시 국도대체,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-gyeongsan-road,,,public, -44c6e400-daf0-42a2-90df-945921788f99,인덕원 동탄 복선전철 제7공구,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-indeokwon-dongtan-7,,,public, -2bc22118-9a70-4d5b-8a3f-cf65432a8bbb,인덕원 동탄 복선전철 제3공구,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-indeokwon-dongtan-3,,,public, -1134fa6a-9b0b-4702-a1e7-39948c8c451a,제주공공하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-jeju-sewage,,,public, -36aec47e-90fc-42cb-8229-3e20423d0424,성남시생활폐기물처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-seongnam-waste,,,public, -5b0b806c-f189-46ea-8771-ebdafcf45afa,광탄공공하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-gwangtan-sewage,,,public, -d2323d9a-c959-48c0-831b-4bb71e48b2e5,인천국제공항 화물,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-incheon-air-cargo,,,public, -32a83ce1-03f1-4daa-b60f-8e64fad83ac6,수도권매립지 제2매립장,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-sudokwon-landfill-2,,,public, -e39ba0af-c3e9-429b-91ec-0a3453a5692e,온산하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-onsan-sewage,,,public, -25f51047-3108-4ff3-98f4-b7f5bce334c5,신천공공하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-sincheon-sewage,,,public, -f0ae9e81-65a5-4bab-a98d-79349bbaa501,장량공공하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-jangnyang-sewage,,,public, -b662cfdb-aae3-48b7-b1d3-2ef050dce027,아포공공하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-apo-sewage,,,public, -76808046-cd35-4813-b240-c323291fa2d8,광주공공폐수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-gwangju-wastewater,,,public, -bda54a49-8282-4f91-9773-645e6a1f2a3b,도척 실촌간 도로,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-docheok-silchon-road,,,public, -02a9e89b-a0a0-4202-adde-870194c35351,여주부평천,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-yeoju-bupyeongcheon,,,public, -9b1fb915-f50b-49b9-a9f9-a9089a825b1f,옥정 공공하수처리,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-okjeong-sewage,,,public, -29d9fa54-6d8d-49f5-98ca-2c720aced55e,부천시 굴포천,ORGANIZATION,99199302-f04f-47ad-9f9f-2afe2db9826a,halla-construction-sites,site-bucheon-gulpocheon,,,public, -99199302-f04f-47ad-9f9f-2afe2db9826a,시공현장,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-construction-sites,,,public, -2dbbdbf8-26b2-461e-bbd7-1fe09b4e36ee,안전관리팀,ORGANIZATION,4b81d408-d81c-43c7-9f87-b6c806db4d7b,halla-safety-hq,halla-safety-team,,,public, -4b81d408-d81c-43c7-9f87-b6c806db4d7b,안전관리본부,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-safety-hq,,,public, -69aa9667-4997-41f8-9898-e470cfc778e5,기술영업팀,ORGANIZATION,1512e429-fb95-4c0d-9409-f0a3286061f2,halla-tech-sales-hq,halla-tech-sales-team,,,public, -1512e429-fb95-4c0d-9409-f0a3286061f2,기술영업본부,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-tech-sales-hq,,,public, -6f9e45f7-63fb-464e-b47c-915fa25f782f,설계팀,ORGANIZATION,2ea01ea1-6d09-4997-ba67-73cbae7aa7dd,halla-env-plant-hq,halla-env-plant-design,,,public, -69d6d246-b281-4da7-be83-fede8e3dc5bd,사업관리팀,ORGANIZATION,2ea01ea1-6d09-4997-ba67-73cbae7aa7dd,halla-env-plant-hq,halla-env-project-mgmt,,,public, -2ea01ea1-6d09-4997-ba67-73cbae7aa7dd,환경플랜트사업본부,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-env-plant-hq,,,public, -45519f6d-ba67-42e2-9b54-f80f1a950a8c,사업관리팀,ORGANIZATION,1d5da961-7f32-4032-a86c-26e8edbcb8ee,halla-infra-business-hq,halla-infra-project-mgmt,,,public, -1d5da961-7f32-4032-a86c-26e8edbcb8ee,기반사업본부,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-infra-business-hq,,,public, -03af0690-af91-468a-9892-0152c7309a4b,운영사업실,ORGANIZATION,d656c134-a50b-43b9-8c2d-fb3738dd0f9f,halla-mgmt-support-hq,halla-operations-office,,,public, -6e9b627f-5304-4e7d-99fc-77fc2328d004,경영지원팀,ORGANIZATION,d656c134-a50b-43b9-8c2d-fb3738dd0f9f,halla-mgmt-support-hq,halla-mgmt-support,,,public, -43c0fb29-84dd-49d2-a2b8-33b6659f4607,사업지원팀,ORGANIZATION,d656c134-a50b-43b9-8c2d-fb3738dd0f9f,halla-mgmt-support-hq,halla-business-support,,,public, -d656c134-a50b-43b9-8c2d-fb3738dd0f9f,경영지원본부,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-mgmt-support-hq,,,public, -940cc09c-32f5-4a02-8213-fb02521189d0,영업총괄,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-general-sales,,,public, -57496cae-a081-4836-a20e-75c78b62257f,업무총괄,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-general-business,,,public, -81e94e6c-e27a-4e36-b0f9-bf8823c96493,임원실,ORGANIZATION,5a03efd2-e62f-4243-800d-58334bf48b2f,halla,halla-executive,,,public, -786dd00c-b0c1-4db9-b25b-1afecd6a7a41,안전관리,ORGANIZATION,94c23f79-a213-4a9e-9c5d-7751777f2fe8,js-construction-hq,js-safety-management,,,public, -5fbf6f2c-6b12-4124-a457-d1064dbb8677,현장,ORGANIZATION,94c23f79-a213-4a9e-9c5d-7751777f2fe8,js-construction-hq,js-site,,,public, -dd82bb7b-43d8-4744-ab65-9b47ea492ac4,공무,ORGANIZATION,94c23f79-a213-4a9e-9c5d-7751777f2fe8,js-construction-hq,js-construction-admin,,,public, -94c23f79-a213-4a9e-9c5d-7751777f2fe8,건설본부,ORGANIZATION,b2fcf17f-7085-4bfe-9663-d8a2f2f4b2d6,jangheon-sanup,js-construction-hq,,,public, -4738ed53-51cc-4dcf-9885-44c9feb26760,견적,ORGANIZATION,c6ff3238-f001-4cdd-b00c-0d137a3baff8,js-tech-sales-hq,js-estimation,,,public, -063a7d31-4aa4-4904-9f32-9b092116145e,기술지원,ORGANIZATION,c6ff3238-f001-4cdd-b00c-0d137a3baff8,js-tech-sales-hq,js-tech-support,,,public, -91063309-efb3-48b7-b55b-a6e79c9eb202,영업,ORGANIZATION,c6ff3238-f001-4cdd-b00c-0d137a3baff8,js-tech-sales-hq,js-sales,,,public, -c6ff3238-f001-4cdd-b00c-0d137a3baff8,기술영업본부,ORGANIZATION,b2fcf17f-7085-4bfe-9663-d8a2f2f4b2d6,jangheon-sanup,js-tech-sales-hq,,,public, -64973a3e-102e-4efd-8147-5be720b89c36,임원실,ORGANIZATION,b2fcf17f-7085-4bfe-9663-d8a2f2f4b2d6,jangheon-sanup,jangheon-sanup-executive,,,public, -6f6f64d3-a555-4680-9b37-a276688b2dfa,설계팀,ORGANIZATION,e57cb22c-383e-4489-8c2f-0c5431917e86,ptc,ptc-design,,,public, -f617365d-2d1a-4d1a-a743-82b3599c8946,시공팀,ORGANIZATION,e57cb22c-383e-4489-8c2f-0c5431917e86,ptc,ptc-construction,,,public, -e629fa7d-c945-4952-b79f-1e23ecf9e7cb,사업관리팀,ORGANIZATION,e57cb22c-383e-4489-8c2f-0c5431917e86,ptc,ptc-project-management,,,public, -f19677d0-0e91-4da7-a4a4-57f3b2815154,영업팀,ORGANIZATION,e57cb22c-383e-4489-8c2f-0c5431917e86,ptc,ptc-sales,,,public, -94be067d-f369-4f6b-a6ec-2d063694c929,임원실,ORGANIZATION,e57cb22c-383e-4489-8c2f-0c5431917e86,ptc,ptc-executive,,,public, -07ef09e2-530b-498b-9f25-610500d4acb3,업무지원팀,ORGANIZATION,c18a8284-0008-48aa-9cdf-9f47ab79a2a9,jangheon,jangheon-business-support,,,public, -50b42506-f10c-4cb8-af7b-5b7c6aa63276,품질팀,ORGANIZATION,e83f9477-5a8d-4168-ab3b-93508ef9cbb3,jangheon-production,jangheon-quality,,,public, -f01cc7b9-aaa0-40c1-9124-9e81c28a6e0d,제작2팀,ORGANIZATION,e83f9477-5a8d-4168-ab3b-93508ef9cbb3,jangheon-production,jangheon-fab-2,,,public, -b6cf39a4-6d2d-4de8-9bad-7ecbb54f477e,제작1팀,ORGANIZATION,e83f9477-5a8d-4168-ab3b-93508ef9cbb3,jangheon-production,jangheon-fab-1,,,public, -3dbd0b03-51f5-4d1e-8271-14ff09258dad,철근팀,ORGANIZATION,e83f9477-5a8d-4168-ab3b-93508ef9cbb3,jangheon-production,jangheon-rebar,,,public, -8c74588f-d755-4fd7-b923-26bad7ff0d14,공무팀,ORGANIZATION,e83f9477-5a8d-4168-ab3b-93508ef9cbb3,jangheon-production,jangheon-production-admin,,,public, -e83f9477-5a8d-4168-ab3b-93508ef9cbb3,생산부,ORGANIZATION,c18a8284-0008-48aa-9cdf-9f47ab79a2a9,jangheon,jangheon-production,,,public, -a5b70b22-a7fc-4d01-a7e2-2cc022e808ee,바론컨설턴트,COMPANY,96369f12-6b66-4b2a-a916-d1c99d326f02,baron-group,baroncs,,,public, -4fda8bb7-d6c4-44b7-8da9-e36f2b487732,경영지원부,ORGANIZATION,b2fcf17f-7085-4bfe-9663-d8a2f2f4b2d6,jangheon-sanup,js-mgmt-support,,,public, -6b687038-180a-4141-b8d2-62647068a8ad,안전진단부,ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,safety-diagnosis,,,public, -36ed0b11-6b5f-4ecd-b5b6-d009d1a8a5f9,건설사업부,ORGANIZATION,d255e6da-4298-4e67-a7cc-45c53d7cdb61,construction-management-h,construction-business,,,public, -d255e6da-4298-4e67-a7cc-45c53d7cdb61,건설사업관리본부,ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,construction-management-h,,,public, -e5c94be2-a3ce-4044-8b6c-623a45abb428,상하수도부,ORGANIZATION,e76d0596-c4cc-4a1d-b596-f824823a17b4,land-environment-hq,water-sewerage,,,public, -d149e48b-cdd8-4ebc-9880-7108a599b938,수자원부,ORGANIZATION,e76d0596-c4cc-4a1d-b596-f824823a17b4,land-environment-hq,land-env-water-resources,,,public, -40be0d19-b61a-424a-97d6-a4fa14e342f1,도시계획부,ORGANIZATION,e76d0596-c4cc-4a1d-b596-f824823a17b4,land-environment-hq,land-env-urban-planning,,,public, -1c89dcbc-ebe5-41c8-b1a6-71b1dda7db19,환경평가부,ORGANIZATION,e76d0596-c4cc-4a1d-b596-f824823a17b4,land-environment-hq,land-env-assessment,,,public, -e76d0596-c4cc-4a1d-b596-f824823a17b4,국토환경사업본부,ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,land-environment-hq,,,public, -32f33163-e613-4f21-b285-449afb31346e,지반터널부,ORGANIZATION,c31bcf86-26da-4cd7-8ef3-371f606cbb72,infrastructure-hq,infra-geotech-tunnel,,,public, -1ccb0859-7eda-4f54-8217-3f781ed036ef,구조부,ORGANIZATION,c31bcf86-26da-4cd7-8ef3-371f606cbb72,infrastructure-hq,infra-structures,,,public, -f8ffcb83-b709-48fc-a993-c1caefc1a648,교통부,ORGANIZATION,c31bcf86-26da-4cd7-8ef3-371f606cbb72,infrastructure-hq,traffic,,,public, -cf1818e2-689f-449b-8c99-0ae25953e576,도로부,ORGANIZATION,c31bcf86-26da-4cd7-8ef3-371f606cbb72,infrastructure-hq,infra-road,,,public, -c31bcf86-26da-4cd7-8ef3-371f606cbb72,인프라사업본부,ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,infrastructure-hq,,,public, -1d784637-6926-4671-975b-3e8e455939a2,안전관리부,ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,safety-management,,,public, -a2252cac-b321-493a-b177-2e702da0d77d,영업지원,ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,sales-support,,,public, -445ee8ad-41e3-42fe-902f-e27d937f09c8,인프라 BIM3,ORGANIZATION,d8b41c30-fe0c-4318-9ae3-cae7dcf6c891,infra-solution,infra-bim3,,,public,팀 -52f06c97-9d6f-4819-971b-43303062e193,인프라 BIM2,ORGANIZATION,d8b41c30-fe0c-4318-9ae3-cae7dcf6c891,infra-solution,infra-bim2,,,public,팀 -432b5261-421b-4e5f-914f-32d7d22fd01f,인프라 BIM1,ORGANIZATION,d8b41c30-fe0c-4318-9ae3-cae7dcf6c891,infra-solution,infra-bim1,,,public,팀 -d8b41c30-fe0c-4318-9ae3-cae7dcf6c891,인프라솔루션,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,infra-solution,,,public,디비전 -96f9d9de-e187-4a27-941e-0f57f3b1851a,네이버웍스관리용(바론그룹),ORGANIZATION,96369f12-6b66-4b2a-a916-d1c99d326f02,baron-group,su4,,,private, -d7205737-e7bd-4926-9b76-39f447ff809e,네이버웍스관리용(한맥),ORGANIZATION,369c1843-56af-4344-9c21-0e01197ab861,hanmac,su2,,,private, -a6343ba4-6062-4150-87b7-a9bec4ca34a0,네이버웍스관리용(삼안),ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,su1,,,private, -818c856b-9545-442f-b827-d1c569f200b0,기술개발센터(조직도용),ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,rnd-center,,,public, -93e1f3dc-503f-40f7-8342-31974b5dd33c,해외사업부,ORGANIZATION,ec325e6f-4333-43ae-a52a-77b742eef7ed,overseas-headquarters,overseas-business,,,public, -ec325e6f-4333-43ae-a52a-77b742eef7ed,해외사업본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,overseas-headquarters,,,public, -5b531a28-a222-44de-87f4-8a0560685a25,수력부,ORGANIZATION,f60d0b66-e0c8-46be-88dc-e8bc93b9e149,water-resources-hq,hydropower,,,public, -6c1ed0b6-8141-4ff8-96fb-a84efbe3a2ce,수자원2부,ORGANIZATION,f60d0b66-e0c8-46be-88dc-e8bc93b9e149,water-resources-hq,water-resources-2,,,public, -d59651ea-41d3-45ee-b519-537fc6a97e65,수자원1부,ORGANIZATION,f60d0b66-e0c8-46be-88dc-e8bc93b9e149,water-resources-hq,water-resources-1,,,public, -f60d0b66-e0c8-46be-88dc-e8bc93b9e149,수자원본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,water-resources-hq,,,public, -1596d8d3-e92d-4c95-8e3e-56c824991ee9,물환경3부,ORGANIZATION,9b9b8b70-b058-4f22-a0c3-e63bc0c7e1b8,water-environment-hq,water-environment-3,,,public, -9ff11813-07ae-41d2-b2af-ac4038dafde2,물환경2부,ORGANIZATION,9b9b8b70-b058-4f22-a0c3-e63bc0c7e1b8,water-environment-hq,water-environment-2,,,public, -2e4d1c4f-f859-4bdc-8531-8c2f29d74cc0,물환경1부,ORGANIZATION,9b9b8b70-b058-4f22-a0c3-e63bc0c7e1b8,water-environment-hq,water-environment-1,,,public, -9b9b8b70-b058-4f22-a0c3-e63bc0c7e1b8,물환경본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,water-environment-hq,,,public, -25e9f426-8012-43bd-8d2f-35ad76f0236a,환경평가부,ORGANIZATION,7ee20c20-01a7-4e39-9a58-a2ffdad153dd,railway-headquarters,environment-assessment,,,public, -a4722e17-c3f9-49d9-8535-4c92d03ccaae,철도2부,ORGANIZATION,7ee20c20-01a7-4e39-9a58-a2ffdad153dd,railway-headquarters,railway-2,,,public, -c826ecf7-c2f9-49a1-ab72-0413216999af,철도1부,ORGANIZATION,7ee20c20-01a7-4e39-9a58-a2ffdad153dd,railway-headquarters,railway-1,,,public, -7ee20c20-01a7-4e39-9a58-a2ffdad153dd,철도본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,railway-headquarters,,,public, -a1bca214-1f8a-403d-8f0b-c6c3aa71bb5a,안전진단팀,ORGANIZATION,967f657c-04e2-49c1-9cb9-f6afba27c0fa,structures,safety-inspection,,,public, -967f657c-04e2-49c1-9cb9-f6afba27c0fa,구조부,ORGANIZATION,dfdc84b7-87a1-4383-8df7-5e656193e79c,road-headquarters,structures,,,public, -c172de4d-1e61-47d5-9446-7bb36ea50063,교통계획부,ORGANIZATION,dfdc84b7-87a1-4383-8df7-5e656193e79c,road-headquarters,transport-planning,,,public, -6760a731-d172-4bc4-8ed4-1b625752c4a2,지반터널부,ORGANIZATION,dfdc84b7-87a1-4383-8df7-5e656193e79c,road-headquarters,geotech-tunnel,,,public, -0b07f990-3ddb-45fa-b3d1-6233e3580f71,도로부,ORGANIZATION,dfdc84b7-87a1-4383-8df7-5e656193e79c,road-headquarters,road,,,public, -dfdc84b7-87a1-4383-8df7-5e656193e79c,도로본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,road-headquarters,,,public, -953a07f6-5ff9-46ed-9a16-bd3066ccec12,조경레저부,ORGANIZATION,95d10fc3-b5d1-4613-9b0a-f9e6cae90d83,land-development,landscape-leisure,,,public, -ed9290e8-7296-47fc-b72f-6ccc45765d4e,도시개발부,ORGANIZATION,95d10fc3-b5d1-4613-9b0a-f9e6cae90d83,land-development,urban-development,,,public, -02ab05a1-caa8-41c9-8c9a-91de614724fd,도시계획부,ORGANIZATION,95d10fc3-b5d1-4613-9b0a-f9e6cae90d83,land-development,urban-planning,,,public, -95d10fc3-b5d1-4613-9b0a-f9e6cae90d83,국토개발본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,land-development,,,public, -f36e2211-8cfd-4813-8618-34e606fe73ac,항만부,ORGANIZATION,cd10e627-808b-4c98-8fdd-d47c84ce57d6,plant-headquarters,harbor,,,public, -fec713bf-ac20-4480-bd74-686a2e6d92b3,플랜트2부,ORGANIZATION,cd10e627-808b-4c98-8fdd-d47c84ce57d6,plant-headquarters,plant-2,,,public, -ef3adb8e-3405-4027-a0f8-5d3d5bf11e84,플랜트1부,ORGANIZATION,cd10e627-808b-4c98-8fdd-d47c84ce57d6,plant-headquarters,plant-1,,,public, -cd10e627-808b-4c98-8fdd-d47c84ce57d6,플랜트본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,plant-headquarters,,,public, -99735f38-47d6-490b-ad54-8090b3bef91f,호남지역총괄본부,ORGANIZATION,8bbd9aad-1b53-4504-befb-e8ae8792340f,cm-division,honam-headquarters,,,public, -8bbd9aad-1b53-4504-befb-e8ae8792340f,CM사업부,ORGANIZATION,92b4fd3c-91ac-41d2-8757-9344861b97aa,cm-headquarters,cm-division,,,public, -92b4fd3c-91ac-41d2-8757-9344861b97aa,CM본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,cm-headquarters,,,public, -fe58cad4-1fa6-4b87-a2eb-51b9ac41320e,사업개발실,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,business-development,,,public, -338fb8af-b594-4c41-9984-54ea4b37637b,안전품질관리실,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,safety-quality,,,public, -1edc196d-020c-4519-9ec4-3d23b99076e6,자산경영실,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,asset-management,,,public, -7adb550b-1756-49f6-b6cc-55b7b426ed52,인사총무부,ORGANIZATION,9bf67270-e15e-4278-b407-02dec5672876,business-strategy,hr-admin,,,public, -01fcbee1-df33-4ee9-bf2b-6d9eb81917d9,대외협력팀,ORGANIZATION,a16f49c4-6828-4fde-a164-43099c4560c4,planning,external-relations,,,public, -cdc40c0b-f985-461a-be18-f8c8e82f31e8,재무회계팀,ORGANIZATION,a16f49c4-6828-4fde-a164-43099c4560c4,planning,finance,,,public, -c6aa2133-ded0-451c-b51b-27faa8b56507,PQ팀,ORGANIZATION,a16f49c4-6828-4fde-a164-43099c4560c4,planning,pq-team,,,public, -ca54cffe-ad30-4f9e-983a-88a85c70404d,업무팀,ORGANIZATION,d656c134-a50b-43b9-8c2d-fb3738dd0f9f,halla-mgmt-support-hq,halla-operations,,,public, -a16f49c4-6828-4fde-a164-43099c4560c4,기획부,ORGANIZATION,9bf67270-e15e-4278-b407-02dec5672876,business-strategy,planning,,,public, -9bf67270-e15e-4278-b407-02dec5672876,경영전략본부,ORGANIZATION,9caf62e1-297d-4e8f-870b-61780998bbeb,saman,business-strategy,,,public, -896da8ab-50b7-4a63-abbc-c85037b63acc,시공BIM,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,construction-bim,,,public, -410a25b1-cd84-46d4-b4d6-8627b397ca42,스마트건설,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,smart-construction,,,public, -68c85ffe-5942-42e7-9785-b2c29b18ecb9,수자원,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,water-resources,,,public, -27683cb5-98ac-49cf-ac57-8157d7d2a663,PM,ORGANIZATION,9e5919f5-0839-4d98-b2e8-dee2ef518b1d,gsim-dev,project-management,,,public, -39ad464a-8468-4acb-8f05-29bc828a1576,GSIM,ORGANIZATION,9e5919f5-0839-4d98-b2e8-dee2ef518b1d,gsim-dev,gsim,,,public, -2501e4a7-bf41-48df-afae-76676e0169f1,bCMf,ORGANIZATION,9e5919f5-0839-4d98-b2e8-dee2ef518b1d,gsim-dev,bcmf,,,public, -9e5919f5-0839-4d98-b2e8-dee2ef518b1d,GSIM개발,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,gsim-dev,,,public, -b83c124a-8d5b-4b14-9347-9d1dca2b42fa,웹디자인,ORGANIZATION,f8366e19-d767-46db-a789-49e36d88a8fc,web-solutions,web-design,,,public, -2403109e-1e43-4390-8d2a-6f0566be028e,ERP,ORGANIZATION,f8366e19-d767-46db-a789-49e36d88a8fc,web-solutions,erp,,,public, -54986461-8cf1-4791-b130-8fecbff5f5a2,솔루션개발,ORGANIZATION,f8366e19-d767-46db-a789-49e36d88a8fc,web-solutions,solution-dev,,,public, -f8366e19-d767-46db-a789-49e36d88a8fc,웹솔루션,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,web-solutions,,,public, -ec003372-962a-4d7b-b90a-edf9dfbb6eea,Abut&시공통합관제,ORGANIZATION,d394dcf2-d474-4059-9cbb-0fca343ec38c,graphics,abut-control,,,public, -52266543-a90b-4441-99c6-51f454b6059a,EG-BIM Draw,ORGANIZATION,d394dcf2-d474-4059-9cbb-0fca343ec38c,graphics,eg-bim-draw,,,public, -1d74bebb-c5a1-49d4-bec4-90f0c89ad21f,HmEG,ORGANIZATION,d394dcf2-d474-4059-9cbb-0fca343ec38c,graphics,hmeg,,,public, -87831866-07de-4e3c-b158-28188a3c1edb,Modeler,ORGANIZATION,d394dcf2-d474-4059-9cbb-0fca343ec38c,graphics,modeler,,,public, -d394dcf2-d474-4059-9cbb-0fca343ec38c,그래픽스,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,graphics,,,public, -d43eed1b-3a36-4624-a040-7f2827016df6,Strana,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,strana,,,public, -78f251f6-d35b-422d-92ab-7fabd80bef85,구조물S/W,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,structural-software,,,public, -b06b8821-b74c-44d5-89c4-3a94e9979161,Watch BIM,ORGANIZATION,9b211775-e6b6-4caf-8c98-91249186e3d9,infra-solution-dev,watch-bim,,,public, -79923c99-1aab-4950-b69e-6312636f544f,Primal 평면,ORGANIZATION,9b211775-e6b6-4caf-8c98-91249186e3d9,infra-solution-dev,primal-plan,,,public, -28fba7fd-6af5-43c1-807b-fb97d6eafada,Way Draw,ORGANIZATION,9b211775-e6b6-4caf-8c98-91249186e3d9,infra-solution-dev,way-draw,,,public, -0cec58e8-efbe-4f93-a734-d1b9cc7747b7,비탈면/구조물,ORGANIZATION,9b211775-e6b6-4caf-8c98-91249186e3d9,infra-solution-dev,slope-structures,,,public, -9b211775-e6b6-4caf-8c98-91249186e3d9,인프라솔루션 개발,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,infra-solution-dev,,,public, -f39ef0c8-0ad0-49cd-97ff-b0672591cfe3,단지설계 개발,ORGANIZATION,d015f4fc-6ce1-4258-be37-6c531cf75c6c,cheonjijin,site-design-dev,,,public, -35cc1fdf-6c0e-4b0e-8ce8-1adc918b8cbf,용지도셀,ORGANIZATION,d015f4fc-6ce1-4258-be37-6c531cf75c6c,cheonjijin,land-map-cell,,,public, -c3e59dbc-a315-47f9-8787-2a792a311e32,천지인셀,ORGANIZATION,d015f4fc-6ce1-4258-be37-6c531cf75c6c,cheonjijin,cheonjijin-cell,,,public, -d015f4fc-6ce1-4258-be37-6c531cf75c6c,천지인,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,cheonjijin,,,public, -61e9ed21-e100-475a-a1e6-bdb2a302db95,상하수도,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,water-sewer,,,public, -0e206c1f-8e9b-43c2-91ce-a17bf62854c8,단가산출,ORGANIZATION,0471c240-7080-4648-86a6-5fdecff9e148,cost-control,cost-estimate,,,public, -bc903928-61cb-45a8-9b4a-794f62a9f8a6,공정관리,ORGANIZATION,0471c240-7080-4648-86a6-5fdecff9e148,cost-control,schedule-control,,,public, -0471c240-7080-4648-86a6-5fdecff9e148,CC,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,cost-control,,,public, -fcb4c9d3-ea2c-48c6-92cc-63706f53fa21,터널,ORGANIZATION,582972ce-6b06-48c8-aa30-31a5285e160e,structural-division,tunnel,,,public, -fab2c4ca-4081-4f64-924d-f32bdf2e61b4,CM기획,ORGANIZATION,582972ce-6b06-48c8-aa30-31a5285e160e,structural-division,cm-planning,,,public, -1e1a999a-38f0-40f1-ae89-62e6031a29e0,하부구조,ORGANIZATION,582972ce-6b06-48c8-aa30-31a5285e160e,structural-division,substructure,,,public, -9eb73493-8aa1-43d4-99a8-424b1a7d60be,구조물계획,ORGANIZATION,582972ce-6b06-48c8-aa30-31a5285e160e,structural-division,structure-planning,,,public, -3d147a08-00b9-47c7-940a-d75c36a6ce81,일반구조물,ORGANIZATION,582972ce-6b06-48c8-aa30-31a5285e160e,structural-division,structural-design,,,public, -1d39617e-8e50-4081-bcd5-551e7842f7b3,DfMA,ORGANIZATION,582972ce-6b06-48c8-aa30-31a5285e160e,structural-division,dfma,,,public, -582972ce-6b06-48c8-aa30-31a5285e160e,일반구조물 div,ORGANIZATION,56cd0fd7-b62a-43c0-8db9-74a30468d7cb,tdc,structural-division,,,public, -56cd0fd7-b62a-43c0-8db9-74a30468d7cb,기술개발센터,ORGANIZATION,5530ca6e-c5e6-4bf0-84d6-76c6a8fb70ee,gpdtdc,tdc,,,internal, -59b83b98-b604-4621-8527-b872be47accc,네이버웍스관리용(총센),ORGANIZATION,5530ca6e-c5e6-4bf0-84d6-76c6a8fb70ee,gpdtdc,su3,,,private, -586434c5-3460-458b-b2e5-488b0e77fa21,솔루션통합,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,solution-integration,,,public, -df6a504d-75b9-4a21-a67e-1522cc18111c,협업증진,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,collaboration,,,public, -6f420ad4-2345-43b5-9c2d-7ba0dae8cae4,디자인기획,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,design-planning,,,public, -88d1d1ee-795a-4b67-a8e7-bb0d4d7ac49f,ERP기획,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,erp-planning,,,public, -c6b1266c-564b-4543-baba-d78807a3d1b4,경영기획,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,management-planning,,,public, -3a660456-eceb-472b-a9a9-f2a5b0ce972b,기술기획,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,tech-planning,,,public, -556798a6-b45e-4822-b6f1-42046b6d0001,전산관리TF,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,it-admin-tf,,,internal, -539f598d-e6f1-4fe2-be48-466448d8d803,인재성장,ORGANIZATION,761a8725-9c19-442c-986c-0319e33a5b1e,gpd,talent-growth,,,public, -761a8725-9c19-442c-986c-0319e33a5b1e,총괄기획실,ORGANIZATION,5530ca6e-c5e6-4bf0-84d6-76c6a8fb70ee,gpdtdc,gpd,,,public, -e57cb22c-383e-4489-8c2f-0c5431917e86,PTC,COMPANY,96369f12-6b66-4b2a-a916-d1c99d326f02,baron-group,ptc,,,public, -9607eb7b-04d2-42ab-80fe-780fe21c7e8f,Personal,PERSONAL,,,personal,개인 사용자 기본 루트 테넌트,,public, -5a03efd2-e62f-4243-800d-58334bf48b2f,한라산업개발,ORGANIZATION,96369f12-6b66-4b2a-a916-d1c99d326f02,baron-group,halla,,,public, -b2fcf17f-7085-4bfe-9663-d8a2f2f4b2d6,장헌산업,ORGANIZATION,96369f12-6b66-4b2a-a916-d1c99d326f02,baron-group,jangheon-sanup,,,public, -c18a8284-0008-48aa-9cdf-9f47ab79a2a9,(주)장헌,ORGANIZATION,96369f12-6b66-4b2a-a916-d1c99d326f02,baron-group,jangheon,,,public, -96369f12-6b66-4b2a-a916-d1c99d326f02,바론그룹,COMPANY_GROUP,038326b6-954a-48a7-a85f-efd83f62b82a,hanmac-family,baron-group,네이버웍스 바론그룹 BARONGROUP_DOMAIN_ID,,public, -5530ca6e-c5e6-4bf0-84d6-76c6a8fb70ee,총괄기획&기술개발센터,COMPANY,038326b6-954a-48a7-a85f-efd83f62b82a,hanmac-family,gpdtdc,네이버웍스 총괄기획&기술개발센터 GPDTDC_DOMAIN_ID,baroncs.co.kr,public, -369c1843-56af-4344-9c21-0e01197ab861,한맥기술,COMPANY,038326b6-954a-48a7-a85f-efd83f62b82a,hanmac-family,hanmac,네이버웍스 한맥 HANMAC_DOMAIN_ID,hanmaceng.co.kr,public, -9caf62e1-297d-4e8f-870b-61780998bbeb,삼안,COMPANY,038326b6-954a-48a7-a85f-efd83f62b82a,hanmac-family,saman,네이버웍스 삼안 SAMAN_DOMAIN_ID,samaneng.com,public, -038326b6-954a-48a7-a85f-efd83f62b82a,한맥가족,COMPANY_GROUP,,,hanmac-family,한맥가족 기본 루트 테넌트,,public, diff --git a/test.sh b/test.sh deleted file mode 100644 index 60906348..00000000 --- a/test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -export USERFRONT_FLUTTER_RUN_FLAGS="" -set -- flutter run \ - --wasm \ - ${USERFRONT_FLUTTER_RUN_FLAGS:-} \ - --no-web-resources-cdn - -echo "Count: $#" -for arg in "$@"; do - echo "Arg: $arg" -done diff --git a/user_bulk_gpdtdc.CSV b/user_bulk_gpdtdc.CSV deleted file mode 100644 index eace57f3..00000000 --- a/user_bulk_gpdtdc.CSV +++ /dev/null @@ -1,221 +0,0 @@ -email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1,sub_email -cyhan@samaneng.com,한치영,01041585840,super,rnd-saman,,,,,224382,tech-planning,,책임연구원,,,b24051,b24051@hanmaceng.co.kr -jhshin@samaneng.com,신지호,010-9268-7509,user,rnd-saman,,,,,209171,erp,,책임연구원,,,M20329,m20329@hanmaceng.co.kr -swbae@samaneng.com,배상우,010-4716-5624,user,rnd-saman,,,,,215032,water-sewer,,선임연구원,,,B22062,b22062@hanmaceng.co.kr -hspark1@samaneng.com,박현수,010-3898-1757,user,rnd-saman,,,,,207241,water-sewer,,수석연구원,팀장,,B19206,b19206@hanmaceng.co.kr -smyoo@samaneng.com,유승민,010-9242-2912,user,rnd-saman,,,,,222244,strana,,선임연구원,,,B22058,b22058@hanmaceng.co.kr -mjjeong1@samaneng.com,정명준,010-3062-2026,user,rnd-saman,,,,,216070,solution-dev,,책임연구원,,,M20330,m20330@hanmaceng.co.kr -hjkim3@samaneng.com,김형준,010-4850-8649,user,rnd-saman,,,,,216121,tdc,,수석연구원,,,B16212,hjkim3@hanmaceng.co.kr -ypshim@samaneng.com,심영표,010-3296-1788,user,rnd-saman,,,,,216164,dfma,,수석연구원,팀장,,B16216,ypshim@hanmaceng.co.kr -jnoh@samaneng.com,노준,010-9177-0523,user,rnd-saman,,,,,217155,slope-structures,,수석연구원,,,B17206,jnoh@hanmaceng.co.kr -dwahn@samaneng.com,안대욱,010-6424-1980,user,rnd-saman,,,,,217157,cheonjijin-cell,,책임연구원,,,B10201,dw6092@hanmaceng.co.kr -kwjeong@samaneng.com,정계완,010-2743-8814,user,rnd-saman,,,,,218001,structural-software,,수석연구원,팀장,,B17203,kyewan@hanmaceng.co.kr -mskim7@samaneng.com,김민성,010-7730-8174,user,rnd-saman,,,,,218002,graphics,,수석연구원,,,B16213,mskim@hanmaceng.co.kr -sjyou@samaneng.com,유석준,010-2067-4875,user,rnd-saman,,,,,218003,smart-construction,,수석연구원,,,B16214,sjyou@hanmaceng.co.kr -kjkim1@samaneng.com,김경종,010-9644-7401,user,rnd-saman,,,,,218005,strana,,선임연구원,,,B17315,kjkim@hanmaceng.co.kr -iwlee@samaneng.com,이인우,010-5001-5305,user,rnd-saman,,,,,218007,structural-software,,책임연구원,,,B16305,inwoo772@hanmaceng.co.kr -gbkim@samaneng.com,김규범,010-3341-8624,user,rnd-saman,,,,,218008,land-map-cell,,선임연구원,,,B17308,gyubeom627@hanmaceng.co.kr -yjlee3@samaneng.com,이연재,010-5276-3376,user,rnd-saman,,,,,218009,structural-software,,선임연구원,,,B17309,yeonjae52@hanmaceng.co.kr -itkim@samaneng.com,김일태,010-6500-6873,user,rnd-saman,,,,,218027,structure-planning,,수석연구원,팀장,,B18206,itkim@hanmaceng.co.kr -jychoi1@samaneng.com,최진영,010-8070-0952,user,rnd-saman,,,,,218118,hmeg,,선임연구원,,,B18311,jy_choi@hanmaceng.co.kr -bjkim2@samaneng.com,김병조,010-8592-7983,user,rnd-saman,,,,,218128,infra-bim2,,수석연구원,팀장,,B18212,bjkim@hanmaceng.co.kr -hklee@samaneng.com,이호경,010-4748-1103,user,rnd-saman,,,,,218141,strana,,수석연구원,팀장,,B18215,hklee@hanmaceng.co.kr -hsryu1@samaneng.com,류한솔,010-9955-1825,user,rnd-saman,,,,,218144,primal-plan,,책임연구원,,,B18213,hansol.ryu@hanmaceng.co.kr -hyshin@samaneng.com,신혜영,010-3595-3511,user,rnd-saman,,,,,218145,design-planning,,수석연구원,팀장,,B18214,shy0622@hanmaceng.co.kr -hsyu@samaneng.com,유효식,010-8885-1095,user,rnd-saman,,,,,218151,schedule-control,,책임연구원,,,B18313,hyosik914@hanmaceng.co.kr -hikim@samaneng.com,김현일,010-9491-7161,user,rnd-saman,,,,,219001,substructure,,수석연구원,팀장,,B19201,kajm77@hanmaceng.co.kr -bhyang1@samaneng.com,양병홍,010-6201-0523,user,rnd-saman,,,,,219018,tdc,,부사장,센터장,,B18202,b18202@hanmaceng.co.kr -eklee1@samaneng.com,이은구,010-5672-7889,user,rnd-saman,,,,,219072,water-resources,,책임연구원,팀장,,B19203,lek@hanmaceng.co.kr -wtshin@samaneng.com,신원태,010-2726-0728,user,rnd-saman,,,,,219080,schedule-control,,책임연구원,,,B19204,panic7ka@hanmaceng.co.kr -dwlee2@samaneng.com,이동원,010-2910-3133,user,rnd-saman,,,,,219152,structural-division,,수석연구원,디비전장,,B19309,dwlee2@hanmaceng.co.kr -mskim@samaneng.com,김명식,010-2289-5257,user,rnd-saman,,,,,219154,hmeg,,선임연구원,,,B19310,myungsik@hanmaceng.co.kr -wison@samaneng.com,손원일,010-2430-4219,user,rnd-saman,,,,,219155,site-design-dev,,책임연구원,,,B19311,wison@hanmaceng.co.kr -dhlee@samaneng.com,이동호,010-8708-6817,user,rnd-saman,,,,,220047,infra-bim2,,선임연구원,,,B22056,b22056@hanmaceng.co.kr -ysjang1@samaneng.com,장용섭,010-4701-1006,user,rnd-saman,,,,,220147,way-draw,,책임연구원,,,B20202,yongseop@hanmaceng.co.kr -jahan@samaneng.com,한지아,010-2584-3790,user,rnd-saman,,,,,222057,web-design,,책임연구원,,,B22001,b22001@hanmaceng.co.kr -shkwon@samaneng.com,권순호,010-4432-4117,user,rnd-saman,,,,,222059,design-planning,,연구원,,,B22003,b22003@hanmaceng.co.kr -dlyoo@samaneng.com,유달리,010-9007-9064,user,rnd-saman,,,,,220227,infra-bim3,,책임연구원,,,B20205,b20205@hanmaceng.co.kr -yhjung2@samaneng.com,정요한,010-8867-6046,user,rnd-saman,,,,,220234,cost-control,,수석연구원,팀장,,B20326,b20326@hanmaceng.co.kr -ygkim1@samaneng.com,김윤권,010-4131-1369,user,rnd-saman,,,,,220266,schedule-control,,책임연구원,,,B20333,b20333@hanmaceng.co.kr -jwlee1@samaneng.com,이재원,010-7766-4757,user,rnd-saman,,,,,220271,modeler,,선임연구원,,,B20336,b20336@hanmaceng.co.kr -jhlee2@samaneng.com,이주형,010-7511-5468,user,rnd-saman,,,,,221022,infra-bim2,,선임연구원,,,B21315,b21315@hanmaceng.co.kr -jslee1@samaneng.com,이진수,010-6409-6442,user,rnd-saman,,,,,221040,land-map-cell,,선임연구원,,,B21306,b21306@hanmaceng.co.kr -yski@samaneng.com,기윤서,010-6289-9782,user,rnd-saman,,,,,221052,bcmf,,수석연구원,,,M21309,m21309@hanmaceng.co.kr -kakang@samaneng.com,강근아,010-3066-9589,user,rnd-saman,,,,,221054,eg-bim-draw,,선임연구원,,,M21318,m21318@hanmaceng.co.kr -jwpark8@samaneng.com,박정우,010-4794-0596,user,rnd-saman,,,,,221055,gsim,,선임연구원,,,B21309,b21309@hanmaceng.co.kr -bckim@samaneng.com,김병철,010-3016-7065,user,rnd-saman,,,,,221064,erp,,선임연구원,,,B21319,b21319@hanmaceng.co.kr -jykang1@samaneng.com,강지영,010-3322-6664,user,rnd-saman,,,,,221067,cm-planning,,선임연구원,,,B21320,b21320@hanmaceng.co.kr -ehjung1@samaneng.com,정은혜,010-3378-1154,user,rnd-saman,,,,,221163,design-planning,,책임연구원,,,B21339,b21339@hanmaceng.co.kr -alhong@samaneng.com,홍아름,010-4070-1948,user,rnd-saman,,,,,221184,tech-planning,,수석연구원,,,B21344,b21344@hanmaceng.co.kr -thlee3@samaneng.com,이태훈,010-4527-8434,user,rnd-saman,,,,,221270,tech-planning,,선임연구원,,,B21364,b21364@hanmaceng.co.kr -jsyun@samaneng.com,윤준수,010-9877-8748,user,rnd-saman,,,,,221293,solution-integration,,선임연구원,,,B21367,b21367@hanmaceng.co.kr -sphwang@samaneng.com,황선필,010-5035-5239,user,rnd-saman,,,,,221292,cm-planning,,선임연구원,,,B21368,b21368@hanmaceng.co.kr -jwchoi3@samaneng.com,최정우,010-8963-5736,user,rnd-saman,,,,,221337,water-sewer,,책임연구원,,,B22055,b21316@hanmaceng.co.kr -ngkim@samaneng.com,김남걸,010-2262-5708,user,rnd-saman,,,,,222004,schedule-control,,수석연구원,,,B21372,b21372@hanmaceng.co.kr -yhchoi@samaneng.com,최용혁,010-8513-1451,user,rnd-saman,,,,,222010,structure-planning,,선임연구원,,,B21370,b21370@hanmaceng.co.kr -skkang@samaneng.com,강상구,010-9291-0264,user,rnd-saman,,,,,222060,cm-planning,,선임연구원,,,B22004,b22004@hanmaceng.co.kr -unhuh@samaneng.com,허유나,010-8870-9345,user,rnd-saman,,,,,222073,design-planning,,선임연구원,,,B22011,b22011@hanmaceng.co.kr -chlee@samaneng.com,이창효,010-8725-3372,user,rnd-saman,,,,,222078,dfma,,선임연구원,,,B22019,b22019@hanmaceng.co.kr -mkim2@samaneng.com,임민경,010-8209-9929,user,rnd-saman,,,,,222087,management-planning,,책임연구원,,,B22015,b21365@hanmaceng.co.kr -cichoi@samaneng.com,최창인,010-4645-2808,user,rnd-saman,,,,,222089,substructure,,책임연구원,,,B22016,b22016@hanmaceng.co.kr -hikim2@samaneng.com,김혜인,010-9510-3760,user,rnd-saman,,,,,222123,tech-planning,,선임연구원,,,B22027,b22027@hanmaceng.co.kr -sclee@samaneng.com,이수창,010-7622-2729,user,rnd-saman,,,,,222150,infra-bim1,,선임연구원,,,B22031,b22031@hanmaceng.co.kr -dhkim3@samaneng.com,김도현,010-9396-6726,user,rnd-saman,,,,,222152,bcmf,,선임연구원,,,B22039,b22039@hanmaceng.co.kr -sdjo@samaneng.com,조선두,010-2009-9705,user,rnd-saman,,,,,222155,cm-planning,,책임연구원,팀장,,B22042,b22042@hanmaceng.co.kr -sachoi@samaneng.com,최선아,010-6460-2728,user,rnd-saman,,,,,222156,management-planning,,책임연구원,,,B22036,b22036@hanmaceng.co.kr -yjahn2@samaneng.com,안용주,010-5433-0545,user,rnd-saman,,,,,222157,dfma,,책임연구원,,,B22037,b22037@hanmaceng.co.kr -smlee@samaneng.com,이수문,010-9229-3480,user,rnd-saman,,,,,222158,dfma,,수석연구원,,,B22035,b22035@hanmaceng.co.kr -tskim@samaneng.com,김태식A,010-9965-9940,user,rnd-saman,,,,,222182,design-planning,,책임연구원,,,B22046,b22046@hanmaceng.co.kr -jhkang@samaneng.com,강정훈,010-9891-8798,user,rnd-saman,,,,,222212,strana,,연구원,,,B22048,b22048@hanmaceng.co.kr -jhkim14@samaneng.com,김재현,010-2534-7837,user,rnd-saman,,,,,222231,watch-bim,,수석연구원,,,B22051,b22051@hanmaceng.co.kr -yjchoi1@samaneng.com,최윤진,010-2349-6687,user,rnd-saman,,,,,222240,way-draw,,연구원,,,B22052,b22052@hanmaceng.co.kr -wkkim@samaneng.com,김원기,010-4727-8530,user,rnd-saman,,,,,222242,infra-bim1,,책임연구원,,,B22057, -jhlee@samaneng.com,이준호,010-2514-6898,user,rnd-saman,,,,,223046,structural-software,,연구원,,,B23003,b23003@hanmaceng.co.kr -jhchoi3@samaneng.com,최진헌,010-8638-8079,user,rnd-saman,,,,,222272,strana,,선임연구원,,,B22063,b22063@hanmaceng.co.kr -hulee1@samaneng.com,이한울,010-9271-8997,user,rnd-saman,,,,,222294,web-design,,연구원,,,B22069,b22069@hanmaceng.co.kr -dwkim3@samaneng.com,김도우,010-5008-6104,user,rnd-saman,,,,,223004,cost-estimate,,연구원,,,B22073,b22073@hanmaceng.co.kr -mskim8@samaneng.com,김민수,010-4570-0179,user,rnd-saman,,,,,223006,construction-bim,,책임연구원,,,B22074,b22074@hanmaceng.co.kr -jhjeong1@samaneng.com,정주현,010-7566-8314,user,rnd-saman,,,,,223007,cheonjijin-cell,,연구원,,,B22076,b22076@hanmaceng.co.kr -scbaek@samaneng.com,백순철,010-9619-0437,user,rnd-saman,,,,,223045,cheonjijin-cell,,연구원,,,B23002,b23002@hanmaceng.co.kr -shyeom1@samaneng.com,염승호,010-8835-0501,user,rnd-saman,,,,,223070,solution-integration,,수석연구원,,,B23008,b23008@hanmaceng.co.kr -jskim1@samaneng.com,김진선,010-7415-8300,user,rnd-saman,,,,,223158,solution-dev,,선임연구원,,,B23033,b23033@hanmaceng.co.kr -hyma@samaneng.com,마희연,010-8213-7601,user,rnd-saman,,,,,223089,design-planning,,선임연구원,,,B23015,b23015@hanmaceng.co.kr -dwjung@samaneng.com,정두휘,010-5521-6160,user,rnd-saman,,,,,223099,design-planning,,연구원,,,B23014,b23014@hanmaceng.co.kr -gshong@samaneng.com,홍길수,010-6641-0857,user,rnd-saman,,,,,223100,modeler,,연구원,,,B23019,b23019@hanmaceng.co.kr -marco@samaneng.com,마르코,010-6662-1599,user,rnd-saman,,,,,223105,strana,,선임연구원,,,B23020,b23020@hanmaceng.co.kr -hjjeong1@samaneng.com,정호진,010-7332-8456,user,rnd-saman,,,,,223114,strana,,연구원,,,B23022,b23022@hanmaceng.co.kr -yjlee2@samaneng.com,이예진,010-9262-7530,user,rnd-saman,,,,,223123,design-planning,,선임연구원,,,B23028,b23028@hanmaceng.co.kr -swpark@samaneng.com,박승우,010-5482-6617,user,rnd-saman,,,,,223195,abut-control,,연구원,,,B23038,b23038@hanmaceng.co.kr -hwji@samaneng.com,지현욱,010-9228-8426,user,rnd-saman,,,,,223134,water-resources,,책임연구원,,,B23025,b23025@hanmaceng.co.kr -swseo@samaneng.com,서승완,010-3245-1363,user,rnd-saman,,,,,223135,erp,,선임연구원,,,B23030,b23030@hanmaceng.co.kr -jykim4@samaneng.com,김주영,010-3855-2839,user,rnd-saman,,,,,223138,structural-design,,선임연구원,,,B23031,b23031@hanmaceng.co.kr -jglee1@samaneng.com,이정곤,010-3958-4115,user,rnd-saman,,,,,223184,cost-estimate,,책임연구원,,,B23036,b23036@hanmaceng.co.kr -hmin@samaneng.com,민홍,010-8654-5461,user,rnd-saman,,,,,223313,gsim,,선임연구원,,,B23055,b23055@hanmaceng.co.kr -hwan@samaneng.com,안효원,010-3358-4260,user,rnd-saman,,,,,223228,infra-bim1,,선임연구원,,,B23040,b23040@hanmaceng.co.kr -sihan@samaneng.com,한성일,010-4322-1100,user,rnd-saman,,,,,223226,abut-control,,책임연구원,,,B23042,b23042@hanmaceng.co.kr -jhkim25@samaneng.com,김재환,010-8962-3743,user,rnd-saman,,,,,223229,structural-design,,책임연구원,,,B23041,b23041@hanmaceng.co.kr -gylee1@samaneng.com,이가연,010-2430-5102,user,rnd-saman,,,,,223269,slope-structures,,연구원,,,B23047,b23047@hanmaceng.co.kr -yskim3@samaneng.com,김예서,010-9167-6132,user,rnd-saman,,,,,223280,land-map-cell,,연구원,,,B23051,b23051@hanmaceng.co.kr -jhpyo@samaneng.com,표재학,010-2522-4984,user,rnd-saman,,,,,223281,primal-plan,,연구원,,,B23052,b23052@hanmaceng.co.kr -sjkim6@samaneng.com,김신지,010-7667-8256,user,rnd-saman,,,,,223361,tech-planning,,연구원,,,B23064,b23064@hanmaceng.co.kr -jschoi@samaneng.com,최지수,010-3557-3726,user,rnd-saman,,,,,223385,water-sewer,,연구원,,,B23068,b23068@hanmaceng.co.kr -jsuhm@samaneng.com,엄지숙,010-5399-9030,user,rnd-saman,,,,,224048,eg-bim-draw,,책임연구원,,,B23072,b23072@hanmaceng.co.kr -kbpark@samaneng.com,박경빈,010-9811-7018,user,rnd-saman,,,,,224053,watch-bim,,연구원,,,B24004,b24004@hanmaceng.co.kr -hkyoon@samaneng.com,윤현경,010-4947-0798,user,rnd-saman,,,,,224057,structure-planning,,선임연구원,,,B24005,b24005@hanmaceng.co.kr -jepark1@samaneng.com,박지은,010-3738-7186,user,rnd-saman,,,,,224058,project-management,,연구원,,,B24006,b24006@hanmaceng.co.kr -kmlee1@samaneng.com,이경민,010-3409-1237,user,rnd-saman,,,,,224069,tech-planning,,선임연구원,,,B24009,b24009@hanmaceng.co.kr -sylim1@samaneng.com,임성엽,010-5702-1213,user,rnd-saman,,,,,224070,land-map-cell,,선임연구원,,,B24011,b24011@hanmaceng.co.kr -jgjeon@samaneng.com,전제경,010-3343-5898,user,rnd-saman,,,,,224091,cheonjijin-cell,,연구원,,,B24013,b24013@hanmaceng.co.kr -hgjang@samaneng.com,장한규,010-7561-3369,user,rnd-saman,,,,,224080,dfma,,연구원,,,B24010,b24010@hanmaceng.co.kr -dwham@samaneng.com,함도원,010-7557-2285,user,rnd-saman,,,,,224106,infra-bim3,,연구원,,,B24018,b24018@hanmaceng.co.kr -grmin@samaneng.com,민경록,010-3272-0097,user,rnd-saman,,,,,224234,hmeg,,연구원,,,B24033,b24033@hanmaceng.co.kr -hklee2@samaneng.com,이현경,010-2687-3453,user,rnd-saman,,,,,224265,site-design-dev,,연구원,,,B24035,b24035@hanmaceng.co.kr -hsjin@samaneng.com,진희성,010-6773-0063,user,rnd-saman,,,,,224291,infra-bim1,,연구원,,,B24039,b24039@hanmaceng.co.kr -gakim@samaneng.com,김근아,010-6301-3072,user,rnd-saman,,,,,224286,site-design-dev,,연구원,,,B24038,b24038@hanmaceng.co.kr -jgbyun@samaneng.com,변정안,010-2499-5922,user,rnd-saman,,,,,224361,dfma,,선임연구원,,,B24046,b24046@hanmaceng.co.kr -mspark@samaneng.com,박민선,010-3716-3845,user,rnd-saman,,,,,224353,tunnel,,연구원,,,B24044,b24044@hanmaceng.co.kr -hyhwang@samaneng.com,황호연,010-4927-3201,user,rnd-saman,,,,,224363,water-resources,,연구원,,,B24047,b24047@hanmaceng.co.kr -smlee2@samaneng.com,이상목,010-3470-9973,user,rnd-saman,,,,,224371,tunnel,,연구원,,,B24048,b24048@hanmaceng.co.kr -dhhan1@samaneng.com,한동현,010-3606-0738,user,rnd-saman,,,,,224385,infra-bim2,,연구원,,,B24052,b24052@hanmaceng.co.kr -jhchoi6@samaneng.com,최준호,010-9174-3191,user,rnd-saman,,,,,224394,gsim,,연구원,,,B24057,b24057@hanmaceng.co.kr -mjlee@samaneng.com,이민지,010-3904-5527,user,rnd-saman,,,,,224392,substructure,,연구원,,,B24054,b24054@hanmaceng.co.kr -mjjeong2@samaneng.com,정미정,010-4299-6544,user,rnd-saman,,,,,224391,structure-planning,,연구원,,,B24055,b24055@hanmaceng.co.kr -mklee@samaneng.com,이민규,010-6243-3767,user,rnd-saman,,,,,224398,abut-control,,연구원,,,B24058,b24058@hanmaceng.co.kr -anlee@samaneng.com,이에녹,010-3301-7191,user,rnd-saman,,,,,224402,infra-bim2,,연구원,,,B24060,b24060@hanmaceng.co.kr -bshan@samaneng.com,한반석,010-5052-1706,user,rnd-saman,,,,,225025,infra-bim3,,연구원,,,B25002,b25002@hanmaceng.co.kr -hckim4@samaneng.com,김희철,010-5012-8456,user,rnd-saman,,,,,225083,water-resources,,연구원,,,B25004,b25004@hanmaceng.co.kr -swpark2@samaneng.com,박성원,010-5672-0355,user,rnd-saman,,,,,225084,infra-bim2,,연구원,,,B25003,b25003@hanmaceng.co.kr -yjsung@samaneng.com,성유정,010-8976-2264,user,rnd-saman,,,,,225099,infra-bim1,,연구원,,,B25009,b25009@hanmaceng.co.kr -sjyou1@samaneng.com,유서진,010-8703-8014,user,rnd-saman,,,,,225100,infra-bim3,,연구원,,,B25010,b25010@hanmaceng.co.kr -gukim@samaneng.com,김건우A,010-6643-0460,user,rnd-saman,,,,,225105,gsim,,연구원,,,B25013,b25013@hanmaceng.co.kr -sykim3@samaneng.com,김성엽,010-3818-8608,user,rnd-saman,,,,,225110,infra-bim3,,선임연구원,,,B25011,b25011@hanmaceng.co.kr -jskwon@samaneng.com,권장승,010-7176-7142,user,rnd-saman,,,,,225111,infra-bim1,,연구원,,,B25014,b25014@hanmaceng.co.kr -jyjung1@samaneng.com,정지윤,010-7132-6329,user,rnd-saman,,,,,225140,design-planning,,연구원,,,B25017,b25017@hanmaceng.co.kr -jwjeong1@samaneng.com,정진우,010-5438-6084,user,rnd-saman,,,,,225122,hmeg,,연구원,,,B25016,b25016@hanmaceng.co.kr -cwshin@samaneng.com,신찬웅,010-5538-6590,user,rnd-saman,,,,,225141,watch-bim,,연구원,,,B25018,b25018@hanmaceng.co.kr -jskim2@samaneng.com,김종석,010-9458-1138,user,rnd-saman,,,,,225156,site-design-dev,,선임연구원,,,B25020,b25020@hanmaceng.co.kr -shpark10@samaneng.com,박석현,010-9252-6709,user,rnd-saman,,,,,225161,infra-bim1,,연구원,,,B25021,b25021@hanmaceng.co.kr -hjjung1@samaneng.com,정학재,010-9285-9318,user,rnd-saman,,,,,225162,infra-bim2,,연구원,,,B25022,b25022@hanmaceng.co.kr -hrlee1@samaneng.com,이해랑,010-8628-0094,user,rnd-saman,,,,,225175,modeler,,연구원,,,B25023,b25023@hanmaceng.co.kr -jhsim@samaneng.com,심재훈,010-6633-3366,user,rnd-saman,,,,,225183,tunnel,,수석연구원,,,B25025,b25025@hanmaceng.co.kr -shkim4@samaneng.com,김수현,010-5645-5153,user,rnd-saman,,,,,225215,design-planning,,선임연구원,,,B25027,b25027@hanmaceng.co.kr -smbaek@samaneng.com,백승민,010-7156-8542,user,rnd-saman,,,,,225319,hmeg,,책임연구원,,,B25035,b25035@hanmaceng.co.kr -swpark3@samaneng.com,박상원,010-4794-0148,user,rnd-saman,,,,,225336,cm-planning,,연구원,,,B25036,b25036@hanmaceng.co.kr -smyoun@samaneng.com,윤석무,010-9780-8901,user,rnd-saman,,,,,226049,solution-dev,,연구원,,,B26002,b26002@hanmaceng.co.kr -jhpark4@samaneng.com,박종혁,010-4211-2090,user,rnd-saman,,,,,226072,infra-bim2,,연구원,,,B26003,b26003@hanmaceng.co.kr -dhhong@samaneng.com,홍덕현,010-5360-7314,user,rnd-saman,,,,,226073,structural-design,,연구원,,,B26004,b26004@hanmaceng.co.kr -twchung@hanmaceng.co.kr,정태원,010-2362-3668,user,rnd-hanmac,,,,,twchung,tdc,,사장,,,M21201,ctw@hanmaceng.co.kr -shkim13@hanmaceng.co.kr,김승호,010-4753-3240,user,rnd-hanmac,,,,,shkim13,substructure,,수석연구원,,,M02248,soo98soo@hanmaceng.co.kr -jhkim32@hanmaceng.co.kr,김정훈,010-9152-7409,user,rnd-hanmac,,,,,jhkim32,infra-solution,,수석연구원,디비전장,,M04308,hunsing@hanmaceng.co.kr -khseok@hanmaceng.co.kr,곽현석,010-3280-3609,user,rnd-hanmac,,,,,khseok,structure-planning,,수석연구원,,,M06309,hyunss97@hanmaceng.co.kr -eshwang1@hanmaceng.co.kr,황은식,010-8792-9303,user,rnd-hanmac,,,,,eshwang1,infra-bim1,,수석연구원,팀장,,M07302,bobos1101@hanmaceng.co.kr -jjpyo@hanmaceng.co.kr,표종진,010-6406-1225,user,rnd-hanmac,,,,,jjpyo,infra-bim2,,수석연구원,,,M08301,piossy@hanmaceng.co.kr -hslee5@hanmaceng.co.kr,이호성,010-8622-3403,user,rnd-hanmac,,,,,hslee5,gsim-dev,,수석연구원,팀장,,M08303,jpsaviola@hanmaceng.co.kr -hylee4@hanmaceng.co.kr,이화영,010-4720-8841,user,rnd-hanmac,,,,,hylee4,tunnel,,수석연구원,팀장,,M12205,leehy@hanmaceng.co.kr -bjshin@hanmaceng.co.kr,신봉진,010-7189-4043,user,rnd-hanmac,,,,,bjshin,cheonjijin-cell,,수석연구원,,,M17203,bjshin@hanmaceng.co.kr -mjkang4@hanmaceng.co.kr,강명진,010-5158-3696,user,rnd-hanmac,,,,,mjkang4,cheonjijin,,수석연구원,팀장,,M17205,mjkang@hanmaceng.co.kr -msoh1@hanmaceng.co.kr,오문성,010-3319-7853,user,rnd-hanmac,,,,,msoh1,cost-estimate,,수석연구원,,,M18201,ohmunseong@hanmaceng.co.kr -swkim3@pre-cast.co.kr,김상욱,010-4857-3636,user,rnd-baron,,,,,swkim3,structural-design,,수석연구원,팀장,,P11202,p11202@hanmaceng.co.kr -yhkim8@brsw.kr,김윤하,010-3322-7515,user,rnd-baron,,,,,yhkim8,web-solutions,,수석연구원,팀장,,T03225,kyh@hanmaceng.co.kr -mnyoun@hanmaceng.co.kr,문남연,010-4534-4443,user,rnd-hanmac,,,,,mnyoun,infra-solution-dev,,수석연구원,팀장,,T04306,ace97@hanmaceng.co.kr -jgchoi@hanmaceng.co.kr,최정균,010-6737-9212,user,rnd-hanmac,,,,,jgchoi,construction-bim,,책임연구원,,,M26013,b21366@hanmaceng.co.kr -jwkim9@hanmaceng.co.kr,김지웅,010-4714-8160,user,rnd-hanmac,,,,,jwkim9,structural-software,,책임연구원,,,B13301,b13301@hanmaceng.co.kr -jychoi4@hanmaceng.co.kr,최준영,010-3156-1423,user,rnd-hanmac,,,,,jychoi4,eg-bim-draw,,책임연구원,,,B17314,cjy627@hanmaceng.co.kr -sykim5@brsw.kr,김세열,010-9122-6487,user,rnd-baron,,,,,sykim5,structural-software,,책임연구원,,,J15306,j15306@hanmaceng.co.kr -ktlee1@hanmaceng.co.kr,이광태,010-9863-1108,user,rnd-hanmac,,,,,ktlee1,infra-bim1,,책임연구원,,,M13301,ktqoqo@hanmaceng.co.kr -jykim7@pre-cast.co.kr,김지영,010-7412-1729,user,rnd-baron,,,,,jykim7,infra-bim3,,책임연구원,팀장,,M17208,jykim@hanmaceng.co.kr -ysmun@pre-cast.co.kr,문영석,010-2833-5718,user,rnd-baron,,,,,ysmun,hmeg,,선임연구원,,,B20309,munyeongseok@hanmaceng.co.kr -ghkim4@brsw.kr,김근형,010-2622-0967,user,rnd-baron,,,,,ghkim4,eg-bim-draw,,선임연구원,,,B20311,rmsgud1202@hanmaceng.co.kr -jkson@brsw.kr,손제근,010-6421-8791,user,rnd-baron,,,,,jkson,project-management,,선임연구원,,,B24022,b24022@hanmaceng.co.kr -jhmoon2@brsw.kr,문준혁,010-2345-3362,user,rnd-baron,,,,,jhmoon2,infra-bim1,,선임연구원,,,B25028,b25028@hanmaceng.co.kr -bslee2@brsw.kr,이배승,010-7583-8440,user,rnd-baron,,,,,bslee2,infra-bim1,,선임연구원,,,B25031,b25031@hanmaceng.co.kr -dhseo@brsw.kr,서동해,010-6289-9590,user,rnd-baron,,,,,dhseo,eg-bim-draw,,선임연구원,,,B24023,b24023@hanmaceng.co.kr -ybkim1@brsw.kr,김영배,010-6371-1318,user,rnd-baron,,,,,ybkim1,primal-plan,,선임연구원,,,B20327,b20327@hanmaceng.co.kr -jhchoi10@hanmaceng.co.kr,최정혁,010-4800-2603,user,rnd-hanmac,,,,,jhchoi10,tunnel,,선임연구원,,,M20212,jhchoi@hanmaceng.co.kr -hgkim5@hanmaceng.co.kr,김한결,010-8009-6172,user,rnd-hanmac,,,,,hgkim5,erp,,선임연구원,,,M22014,hgk121@hanmaceng.co.kr -cypark2@brsw.kr,박채영,010-4508-4006,user,rnd-baron,,,,,cypark2,watch-bim,,연구원,,,B24026,b24026@hanmaceng.co.kr -jylee8@brsw.kr,이지율,010-8652-9029,user,rnd-baron,,,,,jylee8,modeler,,연구원,,,B24021,b24021@hanmaceng.co.kr -shkang2@brsw.kr,강성호,010-2736-7419,user,rnd-baron,,,,,shkang2,way-draw,,연구원,,,B24024,b24024@hanmaceng.co.kr -yclee1@hanmaceng.co.kr,이예찬,010-4748-6225,user,rnd-hanmac,,,,,yclee1,primal-plan,,연구원,,,M24059,m24059@hanmaceng.co.kr -dgkwak@hanmaceng.co.kr,곽동권,010-6878-1926,user,rnd-hanmac,,,,,dgkwak,infra-bim2,,연구원,,,M24083,m24083@hanmaceng.co.kr -huyoon1@brsw.kr,윤현욱,010-7134-5068,user,rnd-baron,,,,,huyoon1,infra-bim1,,연구원,,,B25030,b25030@hanmaceng.co.kr -lhkim1@brsw.kr,김이훈,010-8778-0797,user,rnd-baron,,,,,lhkim1,infra-bim1,,연구원,,,B25032,b25032@hanmaceng.co.kr -ykshin@hanmaceng.co.kr,신영교,010-7567-2528,user,rnd-hanmac,,,,,ykshin,infra-bim2,,연구원,,,M24068,m24068@hanmaceng.co.kr -jtchoi@brsw.kr,최진태,010-6808-0921,user,rnd-baron,,,,,jtchoi,solution-dev,,연구원,,,B24032,b24032@hanmaceng.co.kr -myyang@brsw.kr,양미연,010-5523-5072,user,rnd-baron,,,,,myyang,web-design,,연구원,,,B25015,b25015@hanmaceng.co.kr -ymjo@brsw.kr,조용민,010-9490-9522,user,rnd-baron,,,,,ymjo,infra-bim1,,연구원,,,B25019,b25019@hanmaceng.co.kr -bwlee1@hanmaceng.co.kr,이병욱A,010-3286-4086,user,rnd-hanmac,,,,,bwlee1,infra-bim2,,연구원,,,M25013,m25013@hanmaceng.co.kr -bglee2@brsw.kr,이병권,010-5097-7600,user,rnd-baron,,,,,bglee2,erp,,연구원,,,B21369,b21369@hanmaceng.co.kr -jcjang@hanmaceng.co.kr,장종찬,010-5463-1677,user,rnd-hanmac,,,,,jcjang,gpd,,사장,,,M02210,jcjang67@hanmaceng.co.kr -hjkwon@brsw.kr,권혁진,010-8721-7453,user,rnd-baron,,,,,hjkwon,solution-integration,,수석연구원,,,B20304,cozyjin@hanmaceng.co.kr -thcho@brsw.kr,조태희,010-7588-8031,user,rnd-baron,,,,,thcho,talent-growth,,수석연구원,팀장,,B22040,b22040@hanmaceng.co.kr -wjkim@brsw.kr,김우진A,010-3218-8381,user,rnd-baron,,,,,wjkim,management-planning,,수석연구원,팀장,,J08305,j08305@hanmaceng.co.kr -hisung@hanmaceng.co.kr,성형일,010-2356-6633,user,rnd-hanmac,,,,,hisung,collaboration,,수석연구원,,,M06203,guddlf12@hanmaceng.co.kr -wgkim2@hanmaceng.co.kr,김원기,010-6283-6786,user,rnd-hanmac,,,,,wgkim2,tech-planning,,수석연구원,팀장,,M07318,kwongi79@hanmaceng.co.kr -hsryu2@brsw.kr,류호성,010-3371-5649,user,rnd-baron,,,,,hsryu2,erp-planning,,수석연구원,팀장,,M20331,m20331@hanmaceng.co.kr -wskim3@hanmaceng.co.kr,김원식,010-8755-6171,user,rnd-hanmac,,,,,wskim3,gpd,,전무이사,,,M19202,kws69@hanmaceng.co.kr -jhpark13@hanmaceng.co.kr,박주한,010-8955-3850,user,rnd-hanmac,,,,,jhpark13,collaboration,,책임연구원,,,M22006,m22006@hanmaceng.co.kr -hsmoon@hanmaceng.co.kr,문형석,010-9136-5338,user,rnd-hanmac,,,,,hsmoon,erp-planning,,책임연구원,,,M21420,moon79@hanmaceng.co.kr -smhan@hanmaceng.co.kr,한승민,010-3189-1514,user,rnd-hanmac,,,,,smhan,collaboration,,선임연구원,,,B23070,b23070@hanmaceng.co.kr -disong@brsw.kr,송대일,010-8627-0921,user,rnd-baron,,,,,disong,erp-planning,,선임연구원,,,B24014,b24014@hanmaceng.co.kr -wjryu@brsw.kr,류원준,010-9191-7771,user,rnd-baron,,,,,wjryu,talent-growth,,선임연구원,,,B24063,b24063@hanmaceng.co.kr -jykim8@hanmaceng.co.kr,김지영A,010-6389-0426,user,rnd-hanmac,,,,,jykim8,solution-integration,,선임연구원,,,M21430,kjy0426@hanmaceng.co.kr -jypark7@hanmaceng.co.kr,박지영,010-9055-4775,user,rnd-hanmac,,,,,jypark7,design-planning,,선임연구원,,,M21438,b23046@hanmaceng.co.kr -hrguk@pre-cast.co.kr,국혜림,010-6477-9711,user,rnd-baron,,,,,hrguk,management-planning,,선임연구원,,,B22038,b22038@hanmaceng.co.kr -hhchoi@brsw.kr,최현호,010-2279-3954,user,rnd-baron,,,,,hhchoi,tech-planning,,선임연구원,,,B22064,b22064@hanmaceng.co.kr -dhhwang@hanmaceng.co.kr,황동환,010-4242-6652,user,rnd-hanmac,,,,,dhhwang,tech-planning,,선임연구원,,,M19314,dhh12@hanmaceng.co.kr -khchoi4@brsw.kr,최근혜,010-3637-0646,user,rnd-baron,,,,,khchoi4,talent-growth,,선임연구원,,,B24008,b24008@hanmaceng.co.kr -biyun@brsw.kr,윤봄이,010-8482-2633,user,rnd-baron,,,,,biyun,design-planning,,선임연구원,,,B24016,b24016@hanmaceng.co.kr -mylee2@brsw.kr,이미영A,010-3007-3044,user,rnd-baron,,,,,mylee2,management-planning,,선임연구원,,,B22041,b22041@hanmaceng.co.kr -ojkwon1@hanmaceng.co.kr,권오재,010-9114-3943,user,rnd-hanmac,,,,,ojkwon1,erp-planning,,선임연구원,,,M24031,m24031@hanmaceng.co.kr -huchoi@pre-cast.co.kr,최혜은,010-3453-2360,user,rnd-baron,,,,,huchoi,design-planning,,선임연구원,,,B23060,b23060@hanmaceng.co.kr -sychae@brsw.kr,채선영,010-9523-0055,user,rnd-baron,,,,,sychae,design-planning,,선임연구원,,,B24027,b24027@hanmaceng.co.kr -yjkim7@hanmaceng.co.kr,김윤재,010-9747-9838,user,rnd-hanmac,,,,,yjkim7,management-planning,,선임연구원,,,M22047,gh.kim@hanmaceng.co.kr -yhchoi3@hanmaceng.co.kr,최영환,010-2905-0933,user,rnd-hanmac,,,,,yhchoi3,design-planning,,선임연구원,,,B16302,cyhwan0933@hanmaceng.co.kr -cyjo@brsw.kr,조찬영,010-6671-2879,user,rnd-baron,,,,,cyjo,tech-planning,,연구원,,,B24028,b24028@hanmaceng.co.kr -yykim@brsw.kr,김용연,010-2777-4695,user,rnd-baron,,,,,yykim,tech-planning,,연구원,,,B24053,b24053@hanmaceng.co.kr -sblee5@brsw.kr,이새봄,010-5704-9685,user,rnd-baron,,,,,sblee5,erp-planning,,연구원,,,B23018,b23018@hanmaceng.co.kr -shjeong@brsw.kr,정성호,010-5201-9028,user,rnd-baron,,,,,shjeong,talent-growth,,연구원,,,B24064,b24064@hanmaceng.co.kr -wgjoo@brsw.kr,주완기,010-4247-0144,user,rnd-baron,,,,,wgjoo,talent-growth,,연구원,,,B22067,b22067@hanmaceng.co.kr -syyang@brsw.kr,양숙영,010-7371-7662,user,rnd-baron,,,,,syyang,design-planning,,연구원,,,B24012,b24012@hanmaceng.co.kr -jskim12@brsw.kr,김정석,010-5209-7757,user,rnd-baron,,,,,jskim12,design-planning,,연구원,,,B24049,b24049@hanmaceng.co.kr