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

@@ -11,8 +11,7 @@
import { useMemo, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { Megaphone, Pencil, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Megaphone } from 'lucide-react';
import { Checkbox } from '@/components/ui/checkbox';
import { TableCell, TableRow } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
@@ -41,13 +40,6 @@ export function PopupList({ initialData }: PopupListProps) {
[router]
);
const handleEdit = useCallback(
(id: string) => {
router.push(`/ko/settings/popup-management/${id}?mode=edit`);
},
[router]
);
const handleCreate = useCallback(() => {
router.push('/ko/settings/popup-management?mode=new');
}, [router]);
@@ -85,7 +77,6 @@ export function PopupList({ initialData }: PopupListProps) {
{ key: 'author', label: '작성자', className: 'w-[100px] text-center' },
{ key: 'createdAt', label: '등록일', className: 'w-[110px] text-center' },
{ key: 'period', label: '기간', className: 'w-[180px] text-center' },
{ key: 'actions', label: '작업', className: 'w-[180px] text-center' },
],
// 클라이언트 사이드 필터링
@@ -148,29 +139,6 @@ export function PopupList({ initialData }: PopupListProps) {
<TableCell className="text-center">
{item.startDate}~{item.endDate}
</TableCell>
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
{handlers.isSelected && (
<div className="flex items-center justify-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleEdit(item.id)}
title="수정"
>
<Pencil className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handlers.onDelete?.(item)}
title="삭제"
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)}
</TableCell>
</TableRow>
);
},
@@ -213,21 +181,6 @@ export function PopupList({ initialData }: PopupListProps) {
<div className="text-sm text-muted-foreground">
: {item.startDate} ~ {item.endDate}
</div>
{handlers.isSelected && (
<div className="flex gap-2 pt-2" onClick={(e) => e.stopPropagation()}>
<Button variant="outline" size="sm" onClick={() => handleEdit(item.id)}>
</Button>
<Button
variant="outline"
size="sm"
className="text-destructive"
onClick={() => handlers.onDelete?.(item)}
>
</Button>
</div>
)}
</div>
</div>
</CardContent>
@@ -235,7 +188,7 @@ export function PopupList({ initialData }: PopupListProps) {
);
},
}),
[handleRowClick, handleEdit, handleCreate]
[handleRowClick, handleCreate]
);
return <UniversalListPage config={config} initialData={initialData} />;

View File

@@ -10,6 +10,7 @@ import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { toast } from 'sonner';
import { usePermission } from '@/hooks/usePermission';
import { cancelSubscription, requestDataExport } from './actions';
import type { SubscriptionInfo } from './types';
import { PLAN_LABELS, SUBSCRIPTION_STATUS_LABELS } from './types';
@@ -36,6 +37,7 @@ const formatCurrency = (amount: number): string => {
};
export function SubscriptionClient({ initialData }: SubscriptionClientProps) {
const { canExport } = usePermission();
const [subscription, setSubscription] = useState<SubscriptionInfo>(initialData);
const [showCancelDialog, setShowCancelDialog] = useState(false);
const [isExporting, setIsExporting] = useState(false);
@@ -101,14 +103,16 @@ export function SubscriptionClient({ initialData }: SubscriptionClientProps) {
icon={CreditCard}
actions={
<div className="flex items-center gap-2">
<Button
variant="outline"
onClick={handleExportData}
disabled={isExporting}
>
<Download className="w-4 h-4 mr-2" />
{isExporting ? '처리 중...' : '자료 내보내기'}
</Button>
{canExport && (
<Button
variant="outline"
onClick={handleExportData}
disabled={isExporting}
>
<Download className="w-4 h-4 mr-2" />
{isExporting ? '처리 중...' : '자료 내보내기'}
</Button>
)}
<Button
variant="outline"
className="border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300"