From ac0c8eb29d4499d7e35f1c14df6dd797f27def0a Mon Sep 17 00:00:00 2001 From: chyoo Date: Fri, 10 Jul 2026 13:14:23 +0900 Subject: [PATCH] manage license app launch policies --- src/components/data-table.tsx | 49 ++- src/pages/operations/AdminManagementPage.tsx | 40 ++- src/pages/operations/LicensesPage.tsx | 312 ++++++++++++++++--- src/pages/operations/NoticesPage.tsx | 40 ++- src/pages/operations/OrganizationsPage.tsx | 106 ++++++- src/pages/operations/UserSessionsPage.tsx | 35 ++- src/pages/operations/operations-api.ts | 135 +++++++- 7 files changed, 620 insertions(+), 97 deletions(-) diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx index eb0f784..6918f01 100644 --- a/src/components/data-table.tsx +++ b/src/components/data-table.tsx @@ -48,7 +48,8 @@ export function DataTableCard({ pageSize = 8, minWidth = 980, loading = false, - emptyMessage = '데이터가 없습니다.' + emptyMessage = '데이터가 없습니다.', + serverPagination }: { title: string; description?: string; @@ -62,12 +63,23 @@ export function DataTableCard({ minWidth?: number; loading?: boolean; emptyMessage?: string; + serverPagination?: { + page: number; + total: number; + onPageChange: (page: number) => void; + onSearchChange?: (keyword: string) => void; + filterValues?: Record; + onFilterChange?: (key: string, value: string) => void; + }; }) { const [search, setSearch] = useState(''); const [filterValues, setFilterValues] = useState>({}); const [page, setPage] = useState(1); + const serverMode = Boolean(serverPagination); + const activeFilterValues = serverPagination?.filterValues ?? filterValues; const filteredRows = useMemo(() => { + if (serverMode) return rows; const normalizedSearch = search.trim().toLowerCase(); return rows.filter((row) => { @@ -75,24 +87,37 @@ export function DataTableCard({ ? getSearchText(row).toLowerCase().includes(normalizedSearch) : true; const matchesFilters = filters.every((filter) => { - const selected = filterValues[filter.key]; + const selected = activeFilterValues[filter.key]; return !selected || filter.getValue(row) === selected; }); return matchesSearch && matchesFilters; }); - }, [filterValues, filters, getSearchText, rows, search]); + }, [activeFilterValues, filters, getSearchText, rows, search, serverMode]); - const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); - const currentPage = Math.min(page, totalPages); - const pageRows = filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize); + const totalCount = serverPagination?.total ?? filteredRows.length; + const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)); + const currentPage = Math.min(serverPagination?.page ?? page, totalPages); + const pageRows = serverMode ? rows : filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize); const flexibleColumnIndex = columns[0]?.key === 'no' && columns.length > 1 ? 1 : -1; const updateFilter = (key: string, value: string) => { + if (serverPagination?.onFilterChange) { + serverPagination.onFilterChange(key, value); + return; + } setFilterValues((prev) => ({ ...prev, [key]: value })); setPage(1); }; + const movePage = (nextPage: number) => { + if (serverPagination) { + serverPagination.onPageChange(nextPage); + return; + } + setPage(nextPage); + }; + return (
@@ -108,7 +133,9 @@ export function DataTableCard({ placeholder={searchPlaceholder} value={search} onChange={(event) => { - setSearch(event.target.value); + const value = event.target.value; + setSearch(value); + serverPagination?.onSearchChange?.(value); setPage(1); }} /> @@ -120,7 +147,7 @@ export function DataTableCard({ {filter.label} setPlanForm((prev) => ({ ...prev, name: event.target.value }))} + /> + + +
+