feat(WEB): 리스트 페이지 권한 시스템 통합 및 중복 권한 로직 제거

- PermissionContext 기능 확장 (권한 조회 액션 추가)
- usePermission 훅 개선
- 회계 모듈 권한 통합: 매입/매출/입금/지출/채권/거래처/어음/일보/부실채권
- 인사 모듈 권한 통합: 근태/카드/급여 관리
- 전자결재 권한 통합: 기안함/결재함
- 게시판/품목/단가/팝업/구독 리스트 권한 적용
- UniversalListPage 권한 연동
- 각 컴포넌트 중복 권한 체크 코드 제거 (-828줄)
- 권한 검증 QA 체크리스트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-03 16:46:48 +09:00
parent e111f7b362
commit 17c16028b1
31 changed files with 1016 additions and 828 deletions

View File

@@ -9,7 +9,6 @@ import {
Clock,
FileX,
Files,
Edit,
} from 'lucide-react';
import { toast } from 'sonner';
import {
@@ -75,6 +74,7 @@ import {
APPROVAL_STATUS_COLORS,
} from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { usePermission } from '@/hooks/usePermission';
// ===== 통계 타입 =====
interface InboxSummary {
@@ -87,6 +87,7 @@ interface InboxSummary {
export function ApprovalBox() {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const { canApprove } = usePermission();
// ===== 상태 관리 =====
const [activeTab, setActiveTab] = useState<ApprovalTabType>('all');
@@ -402,13 +403,6 @@ export function ApprovalBox() {
}
}, [selectedDocument, router]);
const handleEditClick = useCallback(
(item: ApprovalRecord) => {
router.push(`/ko/approval/draft/new?id=${item.id}&mode=edit`);
},
[router]
);
const handleModalCopy = useCallback(() => {
toast.info('문서 복제 기능은 준비 중입니다.');
setIsModalOpen(false);
@@ -508,7 +502,6 @@ export function ApprovalBox() {
{ key: 'approver', label: '결재자' },
{ key: 'draftDate', label: '기안일시' },
{ key: 'status', label: '상태', className: 'text-center' },
{ key: 'actions', label: '작업', className: 'w-[80px] text-center' },
],
tabs: tabs,
@@ -591,7 +584,7 @@ export function ApprovalBox() {
headerActions: ({ selectedItems, onClearSelection }) => (
<>
{selectedItems.size > 0 && (
{selectedItems.size > 0 && canApprove && (
<div className="ml-auto flex gap-2">
<Button
variant="default"
@@ -676,18 +669,6 @@ export function ApprovalBox() {
{APPROVAL_STATUS_LABELS[item.status]}
</Badge>
</TableCell>
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
{isSelected && (
<Button
variant="ghost"
size="sm"
onClick={() => handleEditClick(item)}
title="기안함 수정 페이지로 이동"
>
<Edit className="h-4 w-4" />
</Button>
)}
</TableCell>
</TableRow>
);
},
@@ -720,7 +701,7 @@ export function ApprovalBox() {
</div>
}
actions={
item.status === 'pending' && isSelected ? (
item.status === 'pending' && isSelected && canApprove ? (
<div className="flex gap-2">
<Button
variant="default"
@@ -813,8 +794,8 @@ export function ApprovalBox() {
mode="inbox"
onEdit={handleModalEdit}
onCopy={handleModalCopy}
onApprove={handleModalApprove}
onReject={handleModalReject}
onApprove={canApprove ? handleModalApprove : undefined}
onReject={canApprove ? handleModalReject : undefined}
/>
)}
</>
@@ -834,7 +815,6 @@ export function ApprovalBox() {
filterOption,
sortOption,
handleDocumentClick,
handleEditClick,
approveDialogOpen,
pendingSelectedItems,
handleApproveConfirm,
@@ -848,6 +828,7 @@ export function ApprovalBox() {
handleModalCopy,
handleModalApprove,
handleModalReject,
canApprove,
]
);