feat(WEB): Phase 6 IntegratedDetailTemplate 마이그레이션 완료
Phase 6 마이그레이션 (41개 컴포넌트 완료): - 건설/시공: 협력업체, 시공관리, 기성관리, 발주관리, 계약관리 등 - 영업: 견적관리(V2), 고객관리(V2), 수주관리 - 회계: 청구관리, 매입관리, 매출관리, 거래처관리, 악성채권 등 - 생산: 작업지시, 검수관리 - 출고: 출하관리 - 자재: 입고관리, 재고현황 - 고객센터: 문의관리, 이벤트관리, 공지관리 - 인사: 직원관리 - 설정: 권한관리 주요 변경사항: - 34개 xxxConfig.ts 파일 생성 (설정 기반 페이지 구성) - PageLayout/PageHeader → IntegratedDetailTemplate 통합 - 일관된 타이틀/버튼 영역 (목록, 상세, 수정, 삭제) - 1112줄 코드 감소 (중복 제거) 프로젝트 공통화 현황 분석 문서 추가: - 상세 페이지 62%, 목록 페이지 82% 공통화 달성 - 추가 공통화 기회 및 로드맵 정리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ChevronDown, ChevronRight, Shield, ArrowLeft } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
@@ -32,8 +31,8 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { permissionConfig } from './permissionConfig';
|
||||
import type { Permission, MenuPermission, PermissionType } from './types';
|
||||
|
||||
interface PermissionDetailProps {
|
||||
@@ -213,12 +212,6 @@ export function PermissionDetail({ permission, onBack, onSave, onDelete }: Permi
|
||||
});
|
||||
};
|
||||
|
||||
// 메뉴가 부모 메뉴인지 확인
|
||||
const isParentMenu = (menuId: string): boolean => {
|
||||
const menu = menuStructure.find(m => m.id === menuId);
|
||||
return !!(menu?.children && menu.children.length > 0);
|
||||
};
|
||||
|
||||
// 권한 토글 (자동 저장)
|
||||
const handlePermissionToggle = (menuId: string, permType: PermissionType) => {
|
||||
const newMenuPermissions = menuPermissions.map(mp =>
|
||||
@@ -289,15 +282,84 @@ export function PermissionDetail({ permission, onBack, onSave, onDelete }: Permi
|
||||
});
|
||||
};
|
||||
|
||||
// 삭제
|
||||
const handleDelete = () => setDeleteDialogOpen(true);
|
||||
|
||||
// 삭제 확인
|
||||
const confirmDelete = () => {
|
||||
onDelete?.(permission);
|
||||
setDeleteDialogOpen(false);
|
||||
onBack();
|
||||
};
|
||||
|
||||
// IntegratedDetailTemplate용 삭제 핸들러
|
||||
const handleFormDelete = useCallback(async () => {
|
||||
setDeleteDialogOpen(true);
|
||||
return { success: true };
|
||||
}, []);
|
||||
|
||||
// 폼 콘텐츠 렌더링
|
||||
const renderFormContent = useCallback(() => (
|
||||
<div className="space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">기본 정보</h3>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="perm-name">권한명</Label>
|
||||
<Input
|
||||
id="perm-name"
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
onBlur={handleNameBlur}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="perm-status">상태</Label>
|
||||
<Select value={status} onValueChange={handleStatusChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="active">공개</SelectItem>
|
||||
<SelectItem value="hidden">숨김</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 메뉴별 권한 설정 테이블 */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">메뉴</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b">
|
||||
<TableHead className="w-64 py-4">메뉴</TableHead>
|
||||
{PERMISSION_TYPES.map(pt => (
|
||||
<TableHead key={pt} className="text-center w-24 py-4">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<span className="text-sm font-medium">{PERMISSION_LABELS_MAP[pt]}</span>
|
||||
<Checkbox
|
||||
checked={menuPermissions.length > 0 && menuPermissions.every(mp => mp.permissions[pt])}
|
||||
onCheckedChange={(checked) => handleColumnSelectAll(pt, !!checked)}
|
||||
/>
|
||||
</div>
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{renderMenuRows()}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
), [name, status, menuPermissions, handleNameChange, handleNameBlur, handleStatusChange, handleColumnSelectAll, renderMenuRows]);
|
||||
|
||||
// 메뉴 행 렌더링 (재귀적으로 부모-자식 처리)
|
||||
const renderMenuRows = () => {
|
||||
const rows: React.ReactElement[] = [];
|
||||
@@ -369,90 +431,18 @@ export function PermissionDetail({ permission, onBack, onSave, onDelete }: Permi
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
{/* 페이지 헤더 */}
|
||||
<PageHeader
|
||||
title="권한 상세"
|
||||
description="권한 상세 정보를 관리합니다"
|
||||
icon={Shield}
|
||||
actions={
|
||||
<Button variant="ghost" size="sm" onClick={onBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
목록으로
|
||||
</Button>
|
||||
}
|
||||
<>
|
||||
<IntegratedDetailTemplate
|
||||
config={permissionConfig}
|
||||
mode="view"
|
||||
initialData={permission}
|
||||
itemId={permission.id}
|
||||
onBack={onBack}
|
||||
onDelete={handleFormDelete}
|
||||
renderView={() => renderFormContent()}
|
||||
renderForm={() => renderFormContent()}
|
||||
/>
|
||||
|
||||
{/* 삭제/수정 버튼 (타이틀 아래, 기본정보 위) */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={handleDelete}>
|
||||
삭제
|
||||
</Button>
|
||||
<Button onClick={() => onBack()}>
|
||||
수정
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 기본 정보 */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">기본 정보</h3>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="perm-name">권한명</Label>
|
||||
<Input
|
||||
id="perm-name"
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
onBlur={handleNameBlur}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="perm-status">상태</Label>
|
||||
<Select value={status} onValueChange={handleStatusChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="active">공개</SelectItem>
|
||||
<SelectItem value="hidden">숨김</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 메뉴별 권한 설정 테이블 */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">메뉴</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b">
|
||||
<TableHead className="w-64 py-4">메뉴</TableHead>
|
||||
{PERMISSION_TYPES.map(pt => (
|
||||
<TableHead key={pt} className="text-center w-24 py-4">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<span className="text-sm font-medium">{PERMISSION_LABELS_MAP[pt]}</span>
|
||||
<Checkbox
|
||||
checked={menuPermissions.length > 0 && menuPermissions.every(mp => mp.permissions[pt])}
|
||||
onCheckedChange={(checked) => handleColumnSelectAll(pt, !!checked)}
|
||||
/>
|
||||
</div>
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{renderMenuRows()}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 삭제 확인 다이얼로그 */}
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
@@ -477,6 +467,6 @@ export function PermissionDetail({ permission, onBack, onSave, onDelete }: Permi
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Shield } from 'lucide-react';
|
||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||
|
||||
/**
|
||||
* 권한 상세 페이지 Config
|
||||
*
|
||||
* 참고: 이 config는 타이틀/버튼 영역만 정의
|
||||
* 폼 내용은 renderView/renderForm에서 처리
|
||||
*
|
||||
* 특이사항:
|
||||
* - 인라인 수정 (권한명, 상태)
|
||||
* - 메뉴별 권한 설정 테이블
|
||||
* - 자동 저장
|
||||
*/
|
||||
export const permissionConfig: DetailConfig = {
|
||||
title: '권한 상세',
|
||||
description: '권한 상세 정보를 관리합니다',
|
||||
icon: Shield,
|
||||
basePath: '/settings/permissions',
|
||||
fields: [], // renderView/renderForm 사용으로 필드 정의 불필요
|
||||
gridColumns: 2,
|
||||
actions: {
|
||||
showBack: true,
|
||||
showDelete: true,
|
||||
showEdit: true,
|
||||
backLabel: '목록으로',
|
||||
editLabel: '수정',
|
||||
deleteLabel: '삭제',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user