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

@@ -14,7 +14,6 @@ import {
import { toast } from 'sonner';
import {
getInbox,
getInboxSummary,
approveDocument,
rejectDocument,
approveDocumentsBulk,
@@ -80,17 +79,9 @@ import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { usePermission } from '@/hooks/usePermission';
import { InspectionReportModal } from '@/components/production/WorkOrders/documents/InspectionReportModal';
// ===== 통계 타입 =====
interface InboxSummary {
total: number;
pending: number;
approved: number;
rejected: number;
}
export function ApprovalBox() {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [, startTransition] = useTransition();
const { canApprove } = usePermission();
// ===== 상태 관리 =====
@@ -115,7 +106,7 @@ export function ApprovalBox() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedDocument, setSelectedDocument] = useState<ApprovalRecord | null>(null);
const [modalData, setModalData] = useState<ProposalDocumentData | ExpenseReportDocumentData | ExpenseEstimateDocumentData | LinkedDocumentData | null>(null);
const [isModalLoading, setIsModalLoading] = useState(false);
const [, setIsModalLoading] = useState(false);
// ===== 검사성적서 모달 상태 (work_order 연결 문서용) =====
const [isInspectionModalOpen, setIsInspectionModalOpen] = useState(false);
@@ -390,7 +381,7 @@ export function ApprovalBox() {
drafter,
};
break;
default:
default: {
// 품의서
const uploadedFileUrls = (formData.proposalData?.uploadedFiles || []).map(f =>
`/api/proxy/files/${f.id}/download`
@@ -409,6 +400,7 @@ export function ApprovalBox() {
drafter,
};
break;
}
}
setModalData(convertedData);
@@ -527,12 +519,12 @@ export function ApprovalBox() {
columns: [
{ key: 'no', label: '번호', className: 'w-[60px] text-center' },
{ key: 'documentNo', label: '문서번호' },
{ key: 'approvalType', label: '문서유형' },
{ key: 'title', label: '제목' },
{ key: 'drafter', label: '기안자' },
{ key: 'approver', label: '결재자' },
{ key: 'draftDate', label: '기안일시' },
{ key: 'documentNo', label: '문서번호', copyable: true },
{ key: 'approvalType', label: '문서유형', copyable: true },
{ key: 'title', label: '제목', copyable: true },
{ key: 'drafter', label: '기안자', copyable: true },
{ key: 'approver', label: '결재자', copyable: true },
{ key: 'draftDate', label: '기안일시', copyable: true },
{ key: 'status', label: '상태', className: 'text-center' },
],

View File

@@ -18,7 +18,7 @@ import {
SelectValue,
} from '@/components/ui/select';
import { getClients } from '@/components/accounting/VendorManagement/actions';
import type { ProposalData, UploadedFile } from './types';
import type { ProposalData } from './types';
// 거래처 옵션 타입
interface ClientOption {

View File

@@ -26,12 +26,6 @@ import type {
// API 응답 타입 정의
// ============================================
interface ApiResponse<T> {
success: boolean;
data: T;
message: string;
}
// 비용견적서 API 응답 타입
interface ExpenseEstimateApiItem {
id: number;

View File

@@ -6,7 +6,6 @@ import { ExpenseReportDocument } from './ExpenseReportDocument';
import { ExpenseEstimateDocument } from './ExpenseEstimateDocument';
import { LinkedDocumentContent } from './LinkedDocumentContent';
import type {
DocumentType,
DocumentDetailModalProps,
ProposalDocumentData,
ExpenseReportDocumentData,

View File

@@ -6,12 +6,6 @@ import {
DialogTitle,
VisuallyHidden,
} from '@/components/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import {
Copy,
@@ -19,12 +13,6 @@ import {
X as XIcon,
CheckCircle,
Printer,
Share2,
ChevronDown,
FileText,
Mail,
Phone,
MessageCircle,
Send,
} from 'lucide-react';
import { ProposalDocument } from './ProposalDocument';
@@ -32,7 +20,6 @@ import { ExpenseReportDocument } from './ExpenseReportDocument';
import { ExpenseEstimateDocument } from './ExpenseEstimateDocument';
import { printArea } from '@/lib/print-utils';
import type {
DocumentType,
DocumentDetailModalProps,
ProposalDocumentData,
ExpenseReportDocumentData,
@@ -53,8 +40,6 @@ export function DocumentDetailModal({
onSubmit,
}: DocumentDetailModalProps) {
// 기안함 모드에서 임시저장 상태일 때만 수정/상신 가능
const canEdit = mode === 'inbox' || documentStatus === 'draft';
const canSubmit = mode === 'draft' && documentStatus === 'draft';
const getDocumentTitle = () => {
switch (documentType) {
case 'proposal':
@@ -72,18 +57,6 @@ export function DocumentDetailModal({
printArea({ title: `${getDocumentTitle()} 인쇄` });
};
const handleSharePdf = () => {
};
const handleShareEmail = () => {
};
const handleShareFax = () => {
};
const handleShareKakao = () => {
};
const renderDocument = () => {
switch (documentType) {
case 'proposal':

View File

@@ -9,7 +9,6 @@ import {
Send,
Trash2,
Plus,
Bell,
} from 'lucide-react';
import { toast } from 'sonner';
import {
@@ -49,7 +48,6 @@ import type {
} from '@/components/approval/DocumentDetail/types';
import type {
DraftRecord,
DocumentStatus,
Approver,
SortOption,
FilterOption,
@@ -73,7 +71,7 @@ interface DraftsSummary {
export function DraftBox() {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [, startTransition] = useTransition();
// ===== 상태 관리 =====
const [searchQuery, setSearchQuery] = useState('');
@@ -464,11 +462,11 @@ export function DraftBox() {
columns: [
{ key: 'no', label: '번호', className: 'w-[60px] text-center' },
{ key: 'documentNo', label: '문서번호' },
{ key: 'documentType', label: '문서유형' },
{ key: 'title', label: '제목' },
{ key: 'approvers', label: '결재자' },
{ key: 'draftDate', label: '기안일시' },
{ key: 'documentNo', label: '문서번호', copyable: true },
{ key: 'documentType', label: '문서유형', copyable: true },
{ key: 'title', label: '제목', copyable: true },
{ key: 'approvers', label: '결재자', copyable: true },
{ key: 'draftDate', label: '기안일시', copyable: true },
{ key: 'status', label: '상태', className: 'text-center' },
],
@@ -624,7 +622,7 @@ export function DraftBox() {
),
renderTableRow: (item, index, globalIndex, handlers) => {
const { isSelected, onToggle, onRowClick } = handlers;
const { isSelected, onToggle } = handlers;
return (
<TableRow

View File

@@ -33,7 +33,6 @@ import {
type StatCard,
type TabOption,
} from '@/components/templates/UniversalListPage';
import { DateRangeSelector } from '@/components/molecules/DateRangeSelector';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
// import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { DocumentDetailModalV2 as DocumentDetailModal } from '@/components/approval/DocumentDetail';
@@ -63,7 +62,7 @@ interface ReferenceSummary {
}
export function ReferenceBox() {
const [isPending, startTransition] = useTransition();
const [, startTransition] = useTransition();
// ===== 상태 관리 =====
const [activeTab, setActiveTab] = useState<ReferenceTabType>('all');
@@ -152,14 +151,14 @@ export function ReferenceBox() {
// 마운트 시 1회만 실행 (summary 로드)
useEffect(() => {
loadSummary();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ===== 데이터 로드 (의존성 명시적 관리) =====
// currentPage, searchQuery, filterOption, sortOption, activeTab 변경 시 데이터 재로드
useEffect(() => {
loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage, searchQuery, filterOption, sortOption, activeTab]);
// ===== 검색어/필터/탭 변경 시 페이지 초기화 =====
@@ -383,11 +382,11 @@ export function ReferenceBox() {
// 문서번호, 문서유형, 제목, 기안자, 기안일시, 상태
const tableColumns = useMemo(() => [
{ key: 'no', label: '번호', className: 'w-[60px] text-center' },
{ key: 'documentNo', label: '문서번호' },
{ key: 'approvalType', label: '문서유형' },
{ key: 'title', label: '제목' },
{ key: 'drafter', label: '기안자' },
{ key: 'draftDate', label: '기안일시' },
{ key: 'documentNo', label: '문서번호', copyable: true },
{ key: 'approvalType', label: '문서유형', copyable: true },
{ key: 'title', label: '제목', copyable: true },
{ key: 'drafter', label: '기안자', copyable: true },
{ key: 'draftDate', label: '기안일시', copyable: true },
{ key: 'status', label: '상태', className: 'text-center' },
], []);
@@ -513,7 +512,7 @@ export function ReferenceBox() {
),
renderTableRow: (item, index, globalIndex, handlers) => {
const { isSelected, onToggle, onRowClick } = handlers;
const { isSelected, onToggle, onRowClick: _onRowClick } = handlers;
return (
<TableRow