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:
@@ -103,7 +103,7 @@ function BoardListContent({ boardCode }: { boardCode: string }) {
|
||||
|
||||
// 게시글 목록
|
||||
const [posts, setPosts] = useState<BoardPost[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 필터 및 검색
|
||||
@@ -239,11 +239,11 @@ function BoardListContent({ boardCode }: { boardCode: string }) {
|
||||
// 테이블 컬럼
|
||||
const tableColumns: TableColumn[] = useMemo(() => [
|
||||
{ key: 'no', label: 'No.', className: 'w-[60px] text-center' },
|
||||
{ key: 'title', label: '제목', className: 'min-w-[200px]' },
|
||||
{ key: 'author', label: '작성자', className: 'w-[120px]' },
|
||||
{ key: 'views', label: '조회수', className: 'w-[80px] text-center' },
|
||||
{ key: 'title', label: '제목', className: 'min-w-[200px]', copyable: true },
|
||||
{ key: 'author', label: '작성자', className: 'w-[120px]', copyable: true },
|
||||
{ key: 'views', label: '조회수', className: 'w-[80px] text-center', copyable: true },
|
||||
{ key: 'status', label: '상태', className: 'w-[100px] text-center' },
|
||||
{ key: 'createdAt', label: '등록일', className: 'w-[120px] text-center' },
|
||||
{ key: 'createdAt', label: '등록일', className: 'w-[120px] text-center', copyable: true },
|
||||
], []);
|
||||
|
||||
// 테이블 행 렌더링
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
deleteDynamicBoardPost,
|
||||
} from '@/components/board/DynamicBoard/actions';
|
||||
import { getBoardByCode } from '@/components/board/BoardManagement/actions';
|
||||
import { transformApiToComment, type CommentApiData } from '@/components/customer-center/shared/types';
|
||||
import { transformApiToComment } from '@/components/customer-center/shared/types';
|
||||
import type { PostApiData } from '@/components/customer-center/shared/types';
|
||||
import { sanitizeHTML } from '@/lib/sanitize';
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ function DynamicBoardListContent({ boardCode }: { boardCode: string }) {
|
||||
|
||||
// 게시글 목록
|
||||
const [posts, setPosts] = useState<BoardPost[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 필터 및 검색
|
||||
@@ -246,11 +246,11 @@ function DynamicBoardListContent({ boardCode }: { boardCode: string }) {
|
||||
// 테이블 컬럼
|
||||
const tableColumns: TableColumn[] = useMemo(() => [
|
||||
{ key: 'no', label: 'No.', className: 'w-[60px] text-center' },
|
||||
{ key: 'title', label: '제목', className: 'min-w-[200px]' },
|
||||
{ key: 'author', label: '작성자', className: 'w-[120px]' },
|
||||
{ key: 'views', label: '조회수', className: 'w-[80px] text-center' },
|
||||
{ key: 'title', label: '제목', className: 'min-w-[200px]', copyable: true },
|
||||
{ key: 'author', label: '작성자', className: 'w-[120px]', copyable: true },
|
||||
{ key: 'views', label: '조회수', className: 'w-[80px] text-center', copyable: true },
|
||||
{ key: 'status', label: '상태', className: 'w-[100px] text-center' },
|
||||
{ key: 'createdAt', label: '등록일', className: 'w-[120px] text-center' },
|
||||
{ key: 'createdAt', label: '등록일', className: 'w-[120px] text-center', copyable: true },
|
||||
], []);
|
||||
|
||||
// 테이블 행 렌더링
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { CategoryManagement } from '@/components/business/construction/category-management';
|
||||
|
||||
export default function CategoriesPage() {
|
||||
|
||||
@@ -18,7 +18,7 @@ interface OrderDetailPageProps {
|
||||
|
||||
export default function OrderDetailPage({ params }: OrderDetailPageProps) {
|
||||
const { id } = use(params);
|
||||
const router = useRouter();
|
||||
const _router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view';
|
||||
const [data, setData] = useState<Awaited<ReturnType<typeof getOrderDetailFull>>['data']>(undefined);
|
||||
|
||||
@@ -13,7 +13,7 @@ interface ContractDetailPageProps {
|
||||
|
||||
export default function ContractDetailPage({ params }: ContractDetailPageProps) {
|
||||
const { id } = use(params);
|
||||
const router = useRouter();
|
||||
const _router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view';
|
||||
const [data, setData] = useState<Awaited<ReturnType<typeof getContractDetail>>['data']>(undefined);
|
||||
|
||||
@@ -15,7 +15,7 @@ interface HandoverReportDetailPageProps {
|
||||
|
||||
export default function HandoverReportDetailPage({ params }: HandoverReportDetailPageProps) {
|
||||
const { id } = use(params);
|
||||
const router = useRouter();
|
||||
const _router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view';
|
||||
const [data, setData] = useState<Awaited<ReturnType<typeof getHandoverReportDetail>>['data']>(undefined);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useState, useCallback } from 'react';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { EditableTable, EditableColumn } from '@/components/common/EditableTable';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
|
||||
@@ -75,6 +75,7 @@ export default function AttendancePage() {
|
||||
|
||||
setSiteLocation(finalLocation);
|
||||
} else {
|
||||
// no fallback location needed
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AttendancePage] loadSettings error:', error);
|
||||
|
||||
@@ -11,23 +11,17 @@
|
||||
'use client';
|
||||
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import { useState, useEffect, useMemo, Suspense } from 'react';
|
||||
import { FileText, ArrowLeft, Calendar, User, Clock, MapPin, FileCheck } from 'lucide-react';
|
||||
import { useState, useMemo, Suspense } from 'react';
|
||||
import { FileText, ArrowLeft, Calendar, Clock, MapPin, FileCheck } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
|
||||
|
||||
import { FormSectionSkeleton } from '@/components/ui/skeleton';
|
||||
import { format } from 'date-fns';
|
||||
import { ko } from 'date-fns/locale';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
// 문서 유형 라벨
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CSVUploadPage } from '@/components/hr/EmployeeManagement/CSVUploadPage'
|
||||
import type { Employee } from '@/components/hr/EmployeeManagement/types';
|
||||
|
||||
export default function EmployeeCSVUploadPage() {
|
||||
const handleUpload = (employees: Employee[]) => {
|
||||
const handleUpload = (_employees: Employee[]) => {
|
||||
// TODO: API 연동
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ function EmployeeManagementContent() {
|
||||
toast.error(errorMessage);
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
toast.error('서버 오류가 발생했습니다.');
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Settings, X, Eye, EyeOff } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
|
||||
export interface AuditDisplaySettings {
|
||||
|
||||
@@ -123,7 +123,7 @@ export function Day1ChecklistPanel({
|
||||
검색 결과가 없습니다
|
||||
</div>
|
||||
) : (
|
||||
filteredCategories.map((category, categoryIndex) => {
|
||||
filteredCategories.map((category, _categoryIndex) => {
|
||||
const isExpanded = expandedCategories.has(category.id);
|
||||
const progress = getCategoryProgress(category);
|
||||
const allCompleted = progress.completed === progress.total;
|
||||
|
||||
@@ -334,7 +334,7 @@ export const InspectionModal = ({
|
||||
const handleImportSave = useCallback(async () => {
|
||||
if (!importDocRef.current) return;
|
||||
|
||||
const data = importDocRef.current.getInspectionData();
|
||||
const _data = importDocRef.current.getInspectionData();
|
||||
setIsSaving(true);
|
||||
try {
|
||||
// TODO: 실제 저장 API 연동
|
||||
@@ -354,7 +354,7 @@ export const InspectionModal = ({
|
||||
: docInfo.label;
|
||||
|
||||
// 품질관리서 PDF 업로드 핸들러
|
||||
const handleQualityFileUpload = (file: File) => {
|
||||
const handleQualityFileUpload = (_file: File) => {
|
||||
};
|
||||
|
||||
const handleQualityFileDelete = () => {
|
||||
|
||||
@@ -457,7 +457,7 @@ export const ImportInspectionDocument = forwardRef<ImportInspectionRef, ImportIn
|
||||
});
|
||||
|
||||
// OK/NG 선택 핸들러
|
||||
const handleResultChange = useCallback((itemId: string, result: JudgmentResult) => {
|
||||
const _handleResultChange = useCallback((itemId: string, result: JudgmentResult) => {
|
||||
if (readOnly) return;
|
||||
|
||||
setValues((prev) => {
|
||||
@@ -773,8 +773,8 @@ export const ImportInspectionDocument = forwardRef<ImportInspectionRef, ImportIn
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{inspectionItems.map((item, idx) => {
|
||||
const itemValue = values[item.id];
|
||||
{inspectionItems.map((item, _idx) => {
|
||||
const _itemValue = values[item.id];
|
||||
|
||||
// 그룹핑 정보
|
||||
const hasCategory = !!item.subName;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { Upload, FileText, Download, Trash2, Eye, RefreshCw, X } from 'lucide-react';
|
||||
import { Upload, FileText, Download, Trash2, Eye, RefreshCw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export interface QualityDocumentFile {
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
Plus,
|
||||
Users,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Loader2,
|
||||
Bell,
|
||||
} from "lucide-react";
|
||||
@@ -58,13 +57,13 @@ export default function CustomerAccountManagementPage() {
|
||||
const {
|
||||
clients,
|
||||
pagination,
|
||||
isLoading,
|
||||
isLoading: _isLoading,
|
||||
fetchClients,
|
||||
deleteClient: deleteClientApi,
|
||||
} = useClientList();
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filterType, setFilterType] = useState("all");
|
||||
const [searchTerm, _setSearchTerm] = useState("");
|
||||
const [filterType, _setFilterType] = useState("all");
|
||||
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 20;
|
||||
@@ -176,7 +175,7 @@ export default function CustomerAccountManagementPage() {
|
||||
const paginatedClients = filteredClients;
|
||||
|
||||
// 모바일용 인피니티 스크롤 데이터
|
||||
const mobileClients = filteredClients.slice(0, mobileDisplayCount);
|
||||
const _mobileClients = filteredClients.slice(0, mobileDisplayCount);
|
||||
|
||||
// Intersection Observer를 이용한 인피니티 스크롤
|
||||
useEffect(() => {
|
||||
@@ -262,12 +261,12 @@ export default function CustomerAccountManagementPage() {
|
||||
// 테이블 컬럼 정의 (Hooks 순서 보장을 위해 조건부 return 전에 정의)
|
||||
const tableColumns: TableColumn[] = useMemo(() => [
|
||||
{ key: "rowNumber", label: "번호", className: "px-4" },
|
||||
{ key: "code", label: "코드", className: "px-4", sortable: true },
|
||||
{ key: "code", label: "코드", className: "px-4", sortable: true, copyable: true },
|
||||
{ key: "clientType", label: "구분", className: "px-4", sortable: true },
|
||||
{ key: "name", label: "거래처명", className: "px-4", sortable: true },
|
||||
{ key: "representative", label: "대표자", className: "px-4", sortable: true },
|
||||
{ key: "manager", label: "담당자", className: "px-4", sortable: true },
|
||||
{ key: "phone", label: "전화번호", className: "px-4", sortable: true },
|
||||
{ key: "name", label: "거래처명", className: "px-4", sortable: true, copyable: true },
|
||||
{ key: "representative", label: "대표자", className: "px-4", sortable: true, copyable: true },
|
||||
{ key: "managerName", label: "담당자", className: "px-4", sortable: true, copyable: true },
|
||||
{ key: "phone", label: "전화번호", className: "px-4", sortable: true, copyable: true },
|
||||
], []);
|
||||
|
||||
// 핸들러 - 페이지 기반 네비게이션
|
||||
@@ -275,7 +274,7 @@ export default function CustomerAccountManagementPage() {
|
||||
router.push("/sales/client-management-sales-admin?mode=new");
|
||||
};
|
||||
|
||||
const handleEdit = (customer: Client) => {
|
||||
const _handleEdit = (customer: Client) => {
|
||||
router.push(`/sales/client-management-sales-admin/${customer.id}?mode=edit`);
|
||||
};
|
||||
|
||||
@@ -283,7 +282,7 @@ export default function CustomerAccountManagementPage() {
|
||||
router.push(`/sales/client-management-sales-admin/${customer.id}?mode=view`);
|
||||
};
|
||||
|
||||
const handleDelete = (customerId: string) => {
|
||||
const _handleDelete = (customerId: string) => {
|
||||
setDeleteTargetId(customerId);
|
||||
setIsDeleteDialogOpen(true);
|
||||
};
|
||||
@@ -304,7 +303,7 @@ export default function CustomerAccountManagementPage() {
|
||||
};
|
||||
|
||||
// 체크박스 선택
|
||||
const toggleSelection = (id: string) => {
|
||||
const _toggleSelection = (id: string) => {
|
||||
const newSelection = new Set(selectedItems);
|
||||
if (newSelection.has(id)) {
|
||||
newSelection.delete(id);
|
||||
@@ -314,7 +313,7 @@ export default function CustomerAccountManagementPage() {
|
||||
setSelectedItems(newSelection);
|
||||
};
|
||||
|
||||
const toggleSelectAll = () => {
|
||||
const _toggleSelectAll = () => {
|
||||
if (
|
||||
selectedItems.size === paginatedClients.length &&
|
||||
paginatedClients.length > 0
|
||||
@@ -326,7 +325,7 @@ export default function CustomerAccountManagementPage() {
|
||||
};
|
||||
|
||||
// 일괄 삭제
|
||||
const handleBulkDelete = () => {
|
||||
const _handleBulkDelete = () => {
|
||||
if (selectedItems.size === 0) {
|
||||
toast.error("삭제할 항목을 선택해주세요");
|
||||
return;
|
||||
|
||||
@@ -27,9 +27,6 @@ import {
|
||||
PenLine,
|
||||
Factory,
|
||||
XCircle,
|
||||
FileSpreadsheet,
|
||||
FileCheck,
|
||||
ClipboardList,
|
||||
Eye,
|
||||
CheckCircle2,
|
||||
RotateCcw,
|
||||
@@ -275,12 +272,12 @@ export default function OrderDetailPage() {
|
||||
setIsProductionSuccessDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleViewProductionOrder = () => {
|
||||
const _handleViewProductionOrder = () => {
|
||||
// 생산지시 목록 페이지로 이동 (수주관리 내부)
|
||||
router.push(`/sales/order-management-sales/production-orders`);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
const _handleCancel = () => {
|
||||
setCancelReason("");
|
||||
setCancelDetail("");
|
||||
setIsCancelDialogOpen(true);
|
||||
@@ -432,7 +429,7 @@ export default function OrderDetailPage() {
|
||||
};
|
||||
|
||||
// 수주 삭제
|
||||
const handleDelete = () => {
|
||||
const _handleDelete = () => {
|
||||
setIsDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ import type { Process } from "@/types/process";
|
||||
import { formatAmount } from "@/lib/utils/amount";
|
||||
|
||||
// 수주 정보 타입
|
||||
interface OrderInfo {
|
||||
interface _OrderInfo {
|
||||
orderNumber: string;
|
||||
client: string;
|
||||
siteName: string;
|
||||
@@ -76,7 +76,7 @@ interface PriorityConfig {
|
||||
}
|
||||
|
||||
// 작업지시 카드 타입
|
||||
interface WorkOrderCard {
|
||||
interface _WorkOrderCard {
|
||||
id: string;
|
||||
type: string;
|
||||
orderNumber: string;
|
||||
@@ -86,7 +86,7 @@ interface WorkOrderCard {
|
||||
}
|
||||
|
||||
// 자재 소요량 타입
|
||||
interface MaterialRequirement {
|
||||
interface _MaterialRequirement {
|
||||
materialCode: string;
|
||||
materialName: string;
|
||||
unit: string;
|
||||
@@ -109,7 +109,7 @@ interface ScreenItemDetail {
|
||||
}
|
||||
|
||||
// 가이드레일 BOM 타입
|
||||
interface GuideRailBom {
|
||||
interface _GuideRailBom {
|
||||
type: string;
|
||||
spec: string;
|
||||
code: string;
|
||||
@@ -118,14 +118,14 @@ interface GuideRailBom {
|
||||
}
|
||||
|
||||
// 케이스 BOM 타입
|
||||
interface CaseBom {
|
||||
interface _CaseBom {
|
||||
item: string;
|
||||
length: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
// 하단 마감재 BOM 타입
|
||||
interface BottomFinishBom {
|
||||
interface _BottomFinishBom {
|
||||
item: string;
|
||||
spec: string;
|
||||
length: string;
|
||||
|
||||
@@ -100,7 +100,7 @@ function CreateOrderContent() {
|
||||
} else {
|
||||
toast.error(result.error || "수주 등록에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
toast.error("수주 등록 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
@@ -231,14 +231,14 @@ function OrderListContent() {
|
||||
});
|
||||
|
||||
// 페이지네이션
|
||||
const totalPages = Math.ceil(filteredOrders.length / itemsPerPage);
|
||||
const _totalPages = Math.ceil(filteredOrders.length / itemsPerPage);
|
||||
const paginatedOrders = filteredOrders.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
// 모바일용 인피니티 스크롤 데이터
|
||||
const mobileOrders = filteredOrders.slice(0, mobileDisplayCount);
|
||||
const _mobileOrders = filteredOrders.slice(0, mobileDisplayCount);
|
||||
|
||||
// Intersection Observer를 이용한 인피니티 스크롤
|
||||
useEffect(() => {
|
||||
@@ -367,7 +367,7 @@ function OrderListContent() {
|
||||
|
||||
// 다중 선택 삭제 (IntegratedListTemplateV2에서 확인 후 호출됨)
|
||||
// 템플릿 내부에서 이미 확인 팝업을 처리하므로 바로 삭제 실행
|
||||
const handleBulkDelete = async () => {
|
||||
const _handleBulkDelete = async () => {
|
||||
const selectedIds = Array.from(selectedItems);
|
||||
if (selectedIds.length > 0) {
|
||||
setIsDeleting(true);
|
||||
@@ -532,20 +532,20 @@ function OrderListContent() {
|
||||
// 테이블 컬럼 정의 (16개: 체크박스, 번호, 로트번호, 현장명, 출고예정일, 접수일, 수주처, 제품명, 수신자, 수신주소, 수신처, 배송, 담당자, 틀수, 상태, 비고)
|
||||
const tableColumns: TableColumn[] = useMemo(() => [
|
||||
{ key: "rowNumber", label: "번호", className: "px-2 text-center" },
|
||||
{ key: "lotNumber", label: "로트번호", className: "px-2", sortable: true },
|
||||
{ key: "siteName", label: "현장명", className: "px-2", sortable: true },
|
||||
{ key: "expectedShipDate", label: "출고예정일", className: "px-2", sortable: true },
|
||||
{ key: "orderDate", label: "수주일", className: "px-2", sortable: true },
|
||||
{ key: "client", label: "수주처", className: "px-2", sortable: true },
|
||||
{ key: "productName", label: "제품명", className: "px-2", sortable: true },
|
||||
{ key: "receiver", label: "수신자", className: "px-2", sortable: true },
|
||||
{ key: "receiverAddress", label: "수신주소", className: "px-2", sortable: true },
|
||||
{ key: "receiverPlace", label: "수신처", className: "px-2", sortable: true },
|
||||
{ key: "deliveryMethod", label: "배송", className: "px-2", sortable: true },
|
||||
{ key: "manager", label: "담당자", className: "px-2", sortable: true },
|
||||
{ key: "frameCount", label: "틀수", className: "px-2 text-center", sortable: true },
|
||||
{ key: "lotNumber", label: "로트번호", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "siteName", label: "현장명", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "expectedShipDate", label: "출고예정일", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "orderDate", label: "수주일", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "client", label: "수주처", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "productName", label: "제품명", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "receiver", label: "수신자", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "receiverAddress", label: "수신주소", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "receiverPlace", label: "수신처", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "deliveryMethod", label: "배송", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "manager", label: "담당자", className: "px-2", sortable: true, copyable: true },
|
||||
{ key: "frameCount", label: "틀수", className: "px-2 text-center", sortable: true, copyable: true },
|
||||
{ key: "status", label: "상태", className: "px-2", sortable: true },
|
||||
{ key: "remarks", label: "비고", className: "px-2" },
|
||||
{ key: "remarks", label: "비고", className: "px-2", copyable: true },
|
||||
], []);
|
||||
|
||||
// 테이블 행 렌더링 (16개 컬럼: 체크박스, 번호, 로트번호, 현장명, 출고예정일, 접수일, 수주처, 제품명, 수신자, 수신주소, 수신처, 배송, 담당자, 틀수, 상태, 비고)
|
||||
|
||||
@@ -54,7 +54,6 @@ import type {
|
||||
ProductionOrderDetail,
|
||||
ProductionStatus,
|
||||
ProductionWorkOrder,
|
||||
BomProcessGroup,
|
||||
} from "@/components/production/ProductionOrders/types";
|
||||
|
||||
// 공정 진행 현황 컴포넌트
|
||||
|
||||
@@ -161,14 +161,14 @@ function getStatusBadge(status: ProductionStatus) {
|
||||
// 테이블 컬럼 정의
|
||||
const TABLE_COLUMNS: TableColumn[] = [
|
||||
{ key: "no", label: "번호", className: "w-[60px] text-center" },
|
||||
{ key: "orderNumber", label: "수주번호", className: "min-w-[150px]" },
|
||||
{ key: "siteName", label: "현장명", className: "min-w-[180px]" },
|
||||
{ key: "clientName", label: "거래처", className: "min-w-[120px]" },
|
||||
{ key: "nodeCount", label: "개소", className: "w-[80px] text-center" },
|
||||
{ key: "deliveryDate", label: "납기", className: "w-[110px]" },
|
||||
{ key: "productionOrderedAt", label: "생산지시일", className: "w-[110px]" },
|
||||
{ key: "orderNumber", label: "수주번호", className: "min-w-[150px]", copyable: true },
|
||||
{ key: "siteName", label: "현장명", className: "min-w-[180px]", copyable: true },
|
||||
{ key: "clientName", label: "거래처", className: "min-w-[120px]", copyable: true },
|
||||
{ key: "nodeCount", label: "개소", className: "w-[80px] text-center", copyable: true },
|
||||
{ key: "deliveryDate", label: "납기", className: "w-[110px]", copyable: true },
|
||||
{ key: "productionOrderedAt", label: "생산지시일", className: "w-[110px]", copyable: true },
|
||||
{ key: "status", label: "상태", className: "w-[100px]" },
|
||||
{ key: "workOrderCount", label: "작업지시", className: "w-[80px] text-center" },
|
||||
{ key: "workOrderCount", label: "작업지시", className: "w-[80px] text-center", copyable: true },
|
||||
{ key: "actions", label: "작업", className: "w-[100px] text-center" },
|
||||
];
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ interface PricingDetailPageProps {
|
||||
|
||||
export default function PricingDetailPage({ params }: PricingDetailPageProps) {
|
||||
const { id } = use(params);
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const _router = useRouter();
|
||||
const _searchParams = useSearchParams();
|
||||
const mode: 'create' | 'edit' = 'edit';
|
||||
const [data, setData] = useState<PricingData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function QuoteDetailPage() {
|
||||
if (calcResult.success && calcResult.data?.items) {
|
||||
|
||||
// 재계산 결과를 locations에 적용
|
||||
const updatedLocations = v2Data.locations.map((loc, index) => {
|
||||
const updatedLocations = v2Data.locations.map((loc, _index) => {
|
||||
// productCode가 있고 bomResult가 없는 경우에만 업데이트
|
||||
if (!loc.bomResult && loc.productCode) {
|
||||
const calcItem = calcResult.data?.items.find(
|
||||
@@ -89,6 +89,7 @@ export default function QuoteDetailPage() {
|
||||
|
||||
v2Data.locations = updatedLocations;
|
||||
} else {
|
||||
// no BOM result to merge
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,12 +192,29 @@ async function proxyRequest(
|
||||
} else {
|
||||
const responseData = await backendResponse.text();
|
||||
|
||||
clientResponse = new NextResponse(responseData, {
|
||||
status: backendResponse.status,
|
||||
headers: {
|
||||
'Content-Type': responseContentType,
|
||||
},
|
||||
});
|
||||
// 백엔드가 HTML 에러 페이지를 반환한 경우 (404/500 등)
|
||||
// HTML을 그대로 전달하면 클라이언트 response.json()에서 SyntaxError 발생
|
||||
// → 안전한 JSON 에러 응답으로 변환
|
||||
if (!backendResponse.ok && !responseData.trimStart().startsWith('{') && !responseData.trimStart().startsWith('[')) {
|
||||
const status = backendResponse.status;
|
||||
clientResponse = NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: status === 404
|
||||
? '요청한 API를 찾을 수 없습니다.'
|
||||
: `서버 오류가 발생했습니다. (${status})`,
|
||||
error: { code: status },
|
||||
},
|
||||
{ status }
|
||||
);
|
||||
} else {
|
||||
clientResponse = new NextResponse(responseData, {
|
||||
status: backendResponse.status,
|
||||
headers: {
|
||||
'Content-Type': responseContentType,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 토큰이 갱신되었으면 새 쿠키 설정
|
||||
|
||||
Reference in New Issue
Block a user