feat: ESLint 정리 및 전체 코드 품질 개선

- eslint.config.mjs 규칙 강화 및 정리
- 전역 unused import/변수 제거 (312개 파일)
- next.config.ts, middleware, proxy route 개선
- CopyableCell molecule 추가
- 회계/결재/HR/생산/건설/품질/영업 등 전 도메인 lint 정리
- IntegratedListTemplateV2, DataTable, MobileCard 등 공통 컴포넌트 개선
- execute-server-action 에러 핸들링 보강
This commit is contained in:
유병철
2026-03-11 10:27:10 +09:00
parent 924726cba1
commit 81affdc441
315 changed files with 1977 additions and 1344 deletions

View File

@@ -40,7 +40,7 @@ import {
type StatCard,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import type { Account, AccountCategory } from './types';
import type { Account } from './types';
import {
BANK_LABELS,
ACCOUNT_CATEGORY_LABELS,
@@ -215,11 +215,11 @@ export function AccountManagement() {
// 테이블 컬럼
columns: [
{ key: 'no', label: 'No.', className: 'text-center w-[60px]' },
{ key: 'category', label: '구분', className: 'min-w-[80px]' },
{ key: 'accountType', label: '유형', className: 'min-w-[80px]' },
{ key: 'institution', label: '금융기관', className: 'min-w-[100px]' },
{ key: 'accountNumber', label: '계좌번호', className: 'min-w-[160px]' },
{ key: 'accountName', label: '계좌명', className: 'min-w-[120px]' },
{ key: 'category', label: '구분', className: 'min-w-[80px]', copyable: true },
{ key: 'accountType', label: '유형', className: 'min-w-[80px]', copyable: true },
{ key: 'institution', label: '금융기관', className: 'min-w-[100px]', copyable: true },
{ key: 'accountNumber', label: '계좌번호', className: 'min-w-[160px]', copyable: true },
{ key: 'accountName', label: '계좌명', className: 'min-w-[120px]', copyable: true },
{ key: 'status', label: '상태', className: 'min-w-[70px]' },
],
@@ -279,7 +279,7 @@ export function AccountManagement() {
item: Account,
_index: number,
globalIndex: number,
handlers: SelectionHandlers & RowClickHandlers<Account>
_handlers: SelectionHandlers & RowClickHandlers<Account>
) => {
return (
<TableRow
@@ -321,8 +321,8 @@ export function AccountManagement() {
renderMobileCard: (
item: Account,
_index: number,
globalIndex: number,
handlers: SelectionHandlers & RowClickHandlers<Account>
_globalIndex: number,
_handlers: SelectionHandlers & RowClickHandlers<Account>
) => {
return (
<ListMobileCard

View File

@@ -1,7 +1,6 @@
'use client';
import { useState } from 'react';
import { X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -83,7 +82,7 @@ export function AddCompanyDialog({ open, onOpenChange }: AddCompanyDialogProps)
}
setAlertOpen(true);
} catch (error) {
} catch (_error) {
setAlertMessage('사업자등록번호 조회 중 오류가 발생했습니다.');
setAlertOpen(true);
} finally {

View File

@@ -1,6 +1,6 @@
'use client';
import { useState, useRef, useCallback, useEffect } from 'react';
import { useState, useCallback, useEffect } from 'react';
import { useDaumPostcode } from '@/hooks/useDaumPostcode';
import { Building2, Plus, Save, X, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';

View File

@@ -7,7 +7,6 @@ import { CalendarDays, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { ContentSkeleton } from '@/components/ui/skeleton';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { QuantityInput } from '@/components/ui/quantity-input';
import { Card, CardContent } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
@@ -27,7 +26,6 @@ import {
MONTH_OPTIONS,
DAY_OPTIONS,
} from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
export function LeavePolicyManagement() {
const [isLoading, setIsLoading] = useState(true);

View File

@@ -49,12 +49,12 @@ export function PaymentHistoryClient({
initialPagination,
}: PaymentHistoryClientProps) {
// ===== 상태 관리 =====
const [sortOption, setSortOption] = useState<SortOption>('latest');
const [, _setSortOption] = useState<SortOption>('latest');
const itemsPerPage = initialPagination.perPage;
// 거래명세서 팝업 상태
const [showInvoiceDialog, setShowInvoiceDialog] = useState(false);
const [selectedPaymentId, setSelectedPaymentId] = useState<string | null>(null);
const [, setSelectedPaymentId] = useState<string | null>(null);
// ===== 거래명세서 버튼 클릭 =====
const handleViewInvoice = useCallback((paymentId: string) => {
@@ -101,11 +101,11 @@ export function PaymentHistoryClient({
// 테이블 컬럼
columns: [
{ key: 'paymentDate', label: '결제일' },
{ key: 'subscriptionName', label: '구독명' },
{ key: 'paymentMethod', label: '결제 수단' },
{ key: 'subscriptionPeriod', label: '구독 기간' },
{ key: 'amount', label: '금액', className: 'text-right' },
{ key: 'paymentDate', label: '결제일', copyable: true },
{ key: 'subscriptionName', label: '구독명', copyable: true },
{ key: 'paymentMethod', label: '결제 수단', copyable: true },
{ key: 'subscriptionPeriod', label: '구독 기간', copyable: true },
{ key: 'amount', label: '금액', className: 'text-right', copyable: true },
{ key: 'invoice', label: '거래명세서', className: 'text-center' },
],
@@ -124,8 +124,8 @@ export function PaymentHistoryClient({
renderTableRow: (
item: PaymentHistory,
index: number,
globalIndex: number,
handlers: SelectionHandlers & RowClickHandlers<PaymentHistory>
_globalIndex: number,
_handlers: SelectionHandlers & RowClickHandlers<PaymentHistory>
) => {
const isLatest = index === 0;
@@ -175,7 +175,7 @@ export function PaymentHistoryClient({
item: PaymentHistory,
index: number,
globalIndex: number,
handlers: SelectionHandlers & RowClickHandlers<PaymentHistory>
_handlers: SelectionHandlers & RowClickHandlers<PaymentHistory>
) => {
const isLatest = globalIndex === 1;

View File

@@ -17,7 +17,7 @@ import {
type UniversalListConfig,
type TableColumn,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import { ListMobileCard } from '@/components/organisms/MobileCard';
import { formatNumber } from '@/lib/utils/amount';
import type { PaymentHistory, SortOption } from './types';
@@ -36,8 +36,8 @@ export function PaymentHistoryManagement({
initialPagination,
}: PaymentHistoryManagementProps) {
// ===== 상태 관리 =====
const [searchQuery, setSearchQuery] = useState('');
const [sortOption, setSortOption] = useState<SortOption>('latest');
const [searchQuery, _setSearchQuery] = useState('');
const [sortOption, _setSortOption] = useState<SortOption>('latest');
const [currentPage, setCurrentPage] = useState(initialPagination.currentPage);
const itemsPerPage = initialPagination.perPage;
@@ -74,7 +74,7 @@ export function PaymentHistoryManagement({
return result;
}, [data, searchQuery, sortOption]);
const paginatedData = useMemo(() => {
const _paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
return filteredData.slice(startIndex, startIndex + itemsPerPage);
}, [filteredData, currentPage, itemsPerPage]);
@@ -88,11 +88,11 @@ export function PaymentHistoryManagement({
// ===== 테이블 컬럼 =====
const tableColumns: TableColumn[] = useMemo(() => [
{ key: 'paymentDate', label: '결제일' },
{ key: 'subscriptionName', label: '구독명' },
{ key: 'paymentMethod', label: '결제 수단' },
{ key: 'subscriptionPeriod', label: '구독 기간' },
{ key: 'amount', label: '금액', className: 'text-right' },
{ key: 'paymentDate', label: '결제일', copyable: true },
{ key: 'subscriptionName', label: '구독명', copyable: true },
{ key: 'paymentMethod', label: '결제 수단', copyable: true },
{ key: 'subscriptionPeriod', label: '구독 기간', copyable: true },
{ key: 'amount', label: '금액', className: 'text-right', copyable: true },
{ key: 'invoice', label: '거래명세서', className: 'text-center' },
], []);
@@ -100,8 +100,8 @@ export function PaymentHistoryManagement({
const renderTableRow = useCallback((
item: PaymentHistory,
index: number,
globalIndex: number,
handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }
_globalIndex: number,
_handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }
) => {
const isLatest = index === 0; // 최신 항목 (초록색 버튼)
@@ -145,7 +145,7 @@ export function PaymentHistoryManagement({
item: PaymentHistory,
index: number,
globalIndex: number,
handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }
_handlers: { isSelected: boolean; onToggle: () => void; onRowClick?: () => void }
) => {
const isLatest = globalIndex === 1;

View File

@@ -140,7 +140,7 @@ export function PermissionDetailClient({ permissionId, isNew = false, mode = 'vi
// 권한 매트릭스 데이터
const [menuTree, setMenuTree] = useState<MenuTreeItem[]>([]);
const [permissionTypes, setPermissionTypes] = useState<string[]>([]);
const [, setPermissionTypes] = useState<string[]>([]);
const [matrix, setMatrix] = useState<PermissionMatrix | null>(null);
// UI 상태

View File

@@ -7,13 +7,11 @@ import {
Shield,
Plus,
Pencil,
Edit,
Trash2,
Settings,
Eye,
EyeOff,
Users,
Loader2,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
@@ -102,7 +100,7 @@ export function PermissionManagement() {
});
}, []);
const toggleSelectAll = useCallback(() => {
const _toggleSelectAll = useCallback(() => {
if (selectedItems.size === filteredData.length && filteredData.length > 0) {
setSelectedItems(new Set());
} else {
@@ -247,10 +245,10 @@ export function PermissionManagement() {
const tableColumns: TableColumn[] = useMemo(() => {
const baseColumns: TableColumn[] = [
{ key: 'index', label: '번호', className: 'text-center w-[80px]' },
{ key: 'name', label: '역할', className: 'flex-1' },
{ key: 'description', label: '설명', className: 'flex-1' },
{ key: 'name', label: '역할', className: 'flex-1', copyable: true },
{ key: 'description', label: '설명', className: 'flex-1', copyable: true },
{ key: 'status', label: '상태', className: 'text-center w-[100px]' },
{ key: 'createdAt', label: '등록일', className: 'text-center w-[120px]' },
{ key: 'createdAt', label: '등록일', className: 'text-center w-[120px]', copyable: true },
];
if (selectedItems.size > 0) {

View File

@@ -71,12 +71,12 @@ export function PopupList({ initialData }: PopupListProps) {
// 테이블 컬럼
columns: [
{ key: 'no', label: '번호', className: 'w-[60px] text-center' },
{ key: 'target', label: '대상', className: 'w-[80px] text-center' },
{ key: 'title', label: '제목', className: 'min-w-[150px]' },
{ key: 'target', label: '대상', className: 'w-[80px] text-center', copyable: true },
{ key: 'title', label: '제목', className: 'min-w-[150px]', copyable: true },
{ key: 'status', label: '상태', className: 'w-[80px] text-center' },
{ 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: 'author', label: '작성자', className: 'w-[100px] text-center', copyable: true },
{ key: 'createdAt', label: '등록일', className: 'w-[110px] text-center', copyable: true },
{ key: 'period', label: '기간', className: 'w-[180px] text-center', copyable: true },
],
// 클라이언트 사이드 필터링

View File

@@ -49,7 +49,7 @@ export function SubscriptionClient({ initialData }: SubscriptionClientProps) {
} else {
toast.error(result.error || '내보내기 요청에 실패했습니다.');
}
} catch (error) {
} catch (_error) {
toast.error('서버 오류가 발생했습니다.');
} finally {
setIsExporting(false);

View File

@@ -6,7 +6,6 @@ import { PageHeader } from '@/components/organisms/PageHeader';
import { Clock, Save, Loader2 } from 'lucide-react';
import { getWorkSetting, updateWorkSetting } from './actions';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { TimePicker } from '@/components/ui/time-picker';
import { Label } from '@/components/ui/label';
import { QuantityInput } from '@/components/ui/quantity-input';