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:
@@ -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' },
|
||||
],
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -26,12 +26,6 @@ import type {
|
||||
// API 응답 타입 정의
|
||||
// ============================================
|
||||
|
||||
interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// 비용견적서 API 응답 타입
|
||||
interface ExpenseEstimateApiItem {
|
||||
id: number;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ExpenseReportDocument } from './ExpenseReportDocument';
|
||||
import { ExpenseEstimateDocument } from './ExpenseEstimateDocument';
|
||||
import { LinkedDocumentContent } from './LinkedDocumentContent';
|
||||
import type {
|
||||
DocumentType,
|
||||
DocumentDetailModalProps,
|
||||
ProposalDocumentData,
|
||||
ExpenseReportDocumentData,
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user