feat(WEB): UniversalListPage 전체 마이그레이션 및 코드 정리

- UniversalListPage/IntegratedListTemplateV2 컴포넌트 기능 개선
- 회계, HR, 건설, 고객센터, 결재, 설정 등 전체 리스트 컴포넌트 마이그레이션
- 테스트 페이지 및 미사용 API 라우트 정리 (board-test, order-management-test 등)
- 미들웨어 토큰 갱신 로직 개선
- AuthenticatedLayout 구조 개선
- claudedocs 문서 업데이트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-16 15:19:09 +09:00
parent 8639eee5df
commit ad493bcea6
90 changed files with 19864 additions and 20305 deletions

View File

@@ -20,11 +20,12 @@ import { Checkbox } from '@/components/ui/checkbox';
import { Badge } from '@/components/ui/badge';
import { TableRow, TableCell } from '@/components/ui/table';
import {
IntegratedListTemplateV2,
UniversalListPage,
type UniversalListConfig,
type TableColumn,
type StatCard,
type TabOption,
} from '@/components/templates/IntegratedListTemplateV2';
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import {
AlertDialog,
@@ -46,8 +47,6 @@ export function PermissionManagement() {
// ===== 상태 관리 =====
const [searchQuery, setSearchQuery] = useState('');
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 20;
// 역할 데이터
const [roles, setRoles] = useState<Role[]>([]);
@@ -136,13 +135,6 @@ export function PermissionManagement() {
return result;
}, [roles, searchQuery, activeTab]);
const paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
return filteredData.slice(startIndex, startIndex + itemsPerPage);
}, [filteredData, currentPage, itemsPerPage]);
const totalPages = Math.ceil(filteredData.length / itemsPerPage);
// ===== 핸들러 =====
const handleAdd = () => {
router.push('/settings/permissions/new');
@@ -272,8 +264,13 @@ export function PermissionManagement() {
}, [selectedItems.size]);
// ===== 테이블 행 렌더링 =====
const renderTableRow = useCallback((item: Role, index: number, globalIndex: number) => {
const isSelected = selectedItems.has(item.id.toString());
const renderTableRow = useCallback((
item: Role,
index: number,
globalIndex: number,
handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }
) => {
const { isSelected, onToggle } = handlers;
const hasSelection = selectedItems.size > 0;
return (
@@ -285,7 +282,7 @@ export function PermissionManagement() {
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={isSelected}
onCheckedChange={() => toggleSelection(item.id.toString())}
onCheckedChange={onToggle}
/>
</TableCell>
<TableCell className="text-center text-muted-foreground">
@@ -341,9 +338,9 @@ export function PermissionManagement() {
item: Role,
index: number,
globalIndex: number,
isSelected: boolean,
onToggle: () => void
handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }
) => {
const { isSelected, onToggle } = handlers;
return (
<ListMobileCard
id={item.id.toString()}
@@ -386,12 +383,17 @@ export function PermissionManagement() {
}, []);
// ===== 헤더 액션 =====
const headerActions = (
const renderHeaderActions = useCallback(({ selectedItems: selItems }: {
onCreate?: () => void;
selectedItems: Set<string>;
onClearSelection: () => void;
onRefresh: () => void;
}) => (
<div className="flex items-center gap-2 flex-wrap ml-auto">
{selectedItems.size > 0 && (
{selItems.size > 0 && (
<Button variant="destructive" onClick={handleBulkDelete}>
<Trash2 className="h-4 w-4 mr-2" />
({selectedItems.size})
({selItems.size})
</Button>
)}
<Button onClick={handleAdd}>
@@ -399,7 +401,7 @@ export function PermissionManagement() {
</Button>
</div>
);
), [handleBulkDelete, handleAdd]);
// ===== 로딩/에러 상태 =====
if (isLoading) {
@@ -415,42 +417,56 @@ export function PermissionManagement() {
);
}
// ===== 목록 화면 =====
return (
<>
<IntegratedListTemplateV2
title="권한관리"
description="역할 기반 권한을 관리합니다"
icon={Shield}
headerActions={headerActions}
stats={statCards}
searchValue={searchQuery}
onSearchChange={setSearchQuery}
searchPlaceholder="역할명, 설명 검색..."
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
tableColumns={tableColumns}
data={paginatedData}
totalCount={filteredData.length}
allData={filteredData}
selectedItems={selectedItems}
onToggleSelection={toggleSelection}
onToggleSelectAll={toggleSelectAll}
getItemId={(item: Role) => item.id.toString()}
onBulkDelete={handleBulkDelete}
renderTableRow={renderTableRow}
renderMobileCard={renderMobileCard}
pagination={{
currentPage,
totalPages,
totalItems: filteredData.length,
itemsPerPage,
onPageChange: setCurrentPage,
}}
/>
// ===== UniversalListPage 설정 =====
const permissionConfig: UniversalListConfig<Role> = {
title: '권한관리',
description: '역할 기반 권한을 관리합니다',
icon: Shield,
basePath: '/settings/permissions',
{/* 삭제 확인 다이얼로그 */}
idField: 'id',
actions: {
getList: async () => ({
success: true,
data: roles,
totalCount: roles.length,
}),
},
columns: tableColumns,
tabs: tabs,
defaultTab: activeTab,
stats: statCards,
searchPlaceholder: '역할명, 설명 검색...',
itemsPerPage: 20,
clientSideFiltering: true,
searchFilter: (item, searchValue) => {
return (
item.name.toLowerCase().includes(searchValue.toLowerCase()) ||
(item.description?.toLowerCase().includes(searchValue.toLowerCase()) ?? false)
);
},
tabFilter: (item, tabValue) => {
if (tabValue === 'all') return true;
if (tabValue === 'visible') return !item.is_hidden;
if (tabValue === 'hidden') return item.is_hidden;
return true;
},
headerActions: renderHeaderActions,
renderTableRow,
renderMobileCard,
renderDialogs: () => (
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
@@ -485,6 +501,27 @@ export function PermissionManagement() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
),
};
// ===== 목록 화면 =====
return (
<UniversalListPage<Role>
config={permissionConfig}
initialData={roles}
initialTotalCount={roles.length}
externalSelection={{
selectedItems,
setSelectedItems,
}}
externalTab={{
activeTab,
setActiveTab,
}}
externalSearch={{
searchValue: searchQuery,
setSearchValue: setSearchQuery,
}}
/>
);
}