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:
67
src/components/common/AccessDenied.tsx
Normal file
67
src/components/common/AccessDenied.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ShieldOff, ArrowLeft, Home } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface AccessDeniedProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
showBackButton?: boolean;
|
||||
showHomeButton?: boolean;
|
||||
}
|
||||
|
||||
export function AccessDenied({
|
||||
title = '접근 권한이 없습니다',
|
||||
description = '이 페이지에 대한 접근 권한이 없습니다. 관리자에게 문의하세요.',
|
||||
showBackButton = true,
|
||||
showHomeButton = true,
|
||||
}: AccessDeniedProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="min-h-[calc(100vh-200px)] flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-lg border border-border/20 bg-card/50 backdrop-blur">
|
||||
<CardHeader className="text-center pb-4">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="w-20 h-20 bg-gradient-to-br from-amber-500/20 to-orange-500/10 rounded-2xl flex items-center justify-center">
|
||||
<ShieldOff className="w-10 h-10 text-amber-500" />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-xl md:text-2xl font-bold text-foreground">
|
||||
{title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="text-center space-y-6">
|
||||
<p className="text-muted-foreground">{description}</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center pt-2">
|
||||
{showBackButton && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
className="rounded-xl"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
이전 페이지
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showHomeButton && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => router.push('/dashboard')}
|
||||
className="rounded-xl"
|
||||
>
|
||||
<Home className="w-4 h-4 mr-2" />
|
||||
대시보드로 이동
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
50
src/components/common/PermissionGuard.tsx
Normal file
50
src/components/common/PermissionGuard.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import { usePermission } from '@/hooks/usePermission';
|
||||
import type { PermissionAction } from '@/lib/permissions/types';
|
||||
|
||||
interface PermissionGuardProps {
|
||||
action: PermissionAction;
|
||||
/** 다른 메뉴 권한 체크 시 URL 직접 지정 (생략하면 현재 URL 자동 매칭) */
|
||||
url?: string;
|
||||
/** 권한 없을 때 대체 UI (기본: 렌더링 안 함) */
|
||||
fallback?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼/영역 레벨 권한 제어 컴포넌트
|
||||
*
|
||||
* @example
|
||||
* // 현재 페이지 기준 (URL 자동매칭)
|
||||
* <PermissionGuard action="delete">
|
||||
* <Button variant="destructive">삭제</Button>
|
||||
* </PermissionGuard>
|
||||
*
|
||||
* // 다른 메뉴 권한 체크
|
||||
* <PermissionGuard action="approve" url="/approval/inbox">
|
||||
* <Button>승인</Button>
|
||||
* </PermissionGuard>
|
||||
*/
|
||||
export function PermissionGuard({
|
||||
action,
|
||||
url,
|
||||
fallback = null,
|
||||
children,
|
||||
}: PermissionGuardProps) {
|
||||
const permission = usePermission(url);
|
||||
|
||||
const actionMap: Record<PermissionAction, boolean> = {
|
||||
view: permission.canView,
|
||||
create: permission.canCreate,
|
||||
update: permission.canUpdate,
|
||||
delete: permission.canDelete,
|
||||
approve: permission.canApprove,
|
||||
};
|
||||
|
||||
if (!actionMap[action]) {
|
||||
return <>{fallback}</>;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
Reference in New Issue
Block a user