feat(WEB): 권한 관리 시스템 구현 및 상세 페이지 권한 통합

- PermissionContext, usePermission 훅, PermissionGuard 컴포넌트 신규 추가
- AccessDenied 접근 거부 페이지 추가
- permissions lib (체커, 매퍼, 타입) 구현
- BadDebtDetail, BoardDetail, LaborDetail, PricingDetail 등 상세 페이지 권한 적용
- ProcessDetail, StepDetail, ItemDetail, PermissionDetail 권한 연동
- RootProvider에 PermissionProvider 통합
- protected layout 권한 체크 추가
- Claude 프로젝트 설정 파일 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-03 10:17:02 +09:00
parent f0987127eb
commit e111f7b362
22 changed files with 1267 additions and 994 deletions

View File

@@ -43,6 +43,7 @@ import {
} from './types';
import { createBadDebt, updateBadDebt, deleteBadDebt, addBadDebtMemo, deleteBadDebtMemo } from './actions';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { usePermission } from '@/hooks/usePermission';
interface BadDebtDetailProps {
mode: 'view' | 'edit' | 'new';
@@ -95,6 +96,7 @@ const getEmptyRecord = (): Omit<BadDebtRecord, 'id' | 'createdAt' | 'updatedAt'>
export function BadDebtDetail({ mode, recordId, initialData }: BadDebtDetailProps) {
const router = useRouter();
const { canUpdate, canDelete } = usePermission();
const isViewMode = mode === 'view';
const isNewMode = mode === 'new';
@@ -346,12 +348,16 @@ export function BadDebtDetail({ mode, recordId, initialData }: BadDebtDetailProp
if (isViewMode) {
return (
<>
<Button variant="outline" className="text-red-500 border-red-200 hover:bg-red-50" onClick={handleDelete} disabled={isLoading}>
{isLoading ? '처리중...' : '삭제'}
</Button>
<Button onClick={handleEdit} className="bg-blue-500 hover:bg-blue-600" disabled={isLoading}>
</Button>
{canDelete && (
<Button variant="outline" className="text-red-500 border-red-200 hover:bg-red-50" onClick={handleDelete} disabled={isLoading}>
{isLoading ? '처리중...' : '삭제'}
</Button>
)}
{canUpdate && (
<Button onClick={handleEdit} className="bg-blue-500 hover:bg-blue-600" disabled={isLoading}>
</Button>
)}
</>
);
}
@@ -365,7 +371,7 @@ export function BadDebtDetail({ mode, recordId, initialData }: BadDebtDetailProp
</Button>
</>
);
}, [isViewMode, isNewMode, isLoading, handleDelete, handleEdit, handleCancel, handleSave, mode]);
}, [isViewMode, isNewMode, isLoading, handleDelete, handleEdit, handleCancel, handleSave, mode, canUpdate, canDelete]);
// 입력 필드 렌더링 헬퍼
const renderField = (