From 22d16bbb91ed5adc53f182ee985d8145c8344f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Mon, 26 Jan 2026 13:39:21 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20DevToolbar=20=EA=B2=AC=EC=A0=81V2=20?= =?UTF-8?q?=EC=B1=84=EC=9A=B0=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DevFillContext: quoteV2 페이지 타입 추가 - DevToolbar: test-new/test/[id] 패턴 및 견적V2 버튼 추가 - DevToolbar: 축소 상태에서 채우기 버튼 표시 - QuoteRegistrationV2: DevFill 훅 연동 (1~5개 랜덤 개소 생성) - 층, 부호, 가로, 세로, 제품, 수량 등 랜덤 데이터 - 로그인 사용자 정보(localStorage) 연동 --- src/components/dev/DevFillContext.tsx | 2 +- src/components/dev/DevToolbar.tsx | 21 ++++ src/components/quotes/QuoteRegistrationV2.tsx | 111 +++++++++++++++--- 3 files changed, 115 insertions(+), 19 deletions(-) diff --git a/src/components/dev/DevFillContext.tsx b/src/components/dev/DevFillContext.tsx index a64c3304..d3ff2ad0 100644 --- a/src/components/dev/DevFillContext.tsx +++ b/src/components/dev/DevFillContext.tsx @@ -16,7 +16,7 @@ import React, { createContext, useContext, useState, useCallback, useEffect, Rea // 지원하는 페이지 타입 export type DevFillPageType = - | 'quote' | 'order' | 'workOrder' | 'workOrderComplete' | 'shipment' // 판매/생산 플로우 + | 'quote' | 'quoteV2' | 'order' | 'workOrder' | 'workOrderComplete' | 'shipment' // 판매/생산 플로우 | 'deposit' | 'withdrawal' | 'purchaseApproval' | 'cardTransaction' // 회계 플로우 | 'client'; // 기준정보 diff --git a/src/components/dev/DevToolbar.tsx b/src/components/dev/DevToolbar.tsx index fe24db78..a742d92f 100644 --- a/src/components/dev/DevToolbar.tsx +++ b/src/components/dev/DevToolbar.tsx @@ -43,6 +43,8 @@ import { useDevFillContext, type DevFillPageType } from './DevFillContext'; // 페이지 경로와 타입 매핑 (pathname만으로 매칭되는 패턴) const PAGE_PATTERNS: { pattern: RegExp; type: DevFillPageType; label: string }[] = [ // 판매/생산 플로우 + { pattern: /\/quote-management\/test-new/, type: 'quoteV2', label: '견적V2' }, + { pattern: /\/quote-management\/test\/\d+/, type: 'quoteV2', label: '견적V2' }, { pattern: /\/quote-management\/new/, type: 'quote', label: '견적' }, { pattern: /\/quote-management\/\d+\/edit/, type: 'quote', label: '견적' }, { pattern: /\/order-management-sales\/new/, type: 'order', label: '수주' }, @@ -67,6 +69,7 @@ const MODE_NEW_PAGES: { pattern: RegExp; type: DevFillPageType; label: string }[ // 플로우 단계 정의 const FLOW_STEPS: { type: DevFillPageType; label: string; icon: typeof FileText; path: string }[] = [ + { type: 'quoteV2', label: '견적V2', icon: FileText, path: '/sales/quote-management/test-new' }, { type: 'quote', label: '견적', icon: FileText, path: '/sales/quote-management/new' }, { type: 'order', label: '수주', icon: ClipboardList, path: '/sales/order-management-sales/new' }, { type: 'workOrder', label: '작업지시', icon: Wrench, path: '/production/work-orders/create' }, @@ -192,6 +195,24 @@ export function DevToolbar() { {flowData.workOrderId && ` → 작업#${flowData.workOrderId}`} )} + {/* 축소 상태에서 채우기 버튼 */} + {!isExpanded && activePage && hasRegisteredForm(activePage) && ( + + )}
{hasFlowData && ( diff --git a/src/components/quotes/QuoteRegistrationV2.tsx b/src/components/quotes/QuoteRegistrationV2.tsx index 719e8e1a..975912ce 100644 --- a/src/components/quotes/QuoteRegistrationV2.tsx +++ b/src/components/quotes/QuoteRegistrationV2.tsx @@ -43,7 +43,8 @@ import { } from "./actions"; import { getClients } from "../accounting/VendorManagement/actions"; import { isNextRedirectError } from "@/lib/utils/redirect-error"; -import { useAuth } from "@/contexts/AuthContext"; +// 실제 로그인 사용자 정보는 localStorage('user')에 저장됨 (LoginPage.tsx 참조) +import { useDevFill } from "@/components/dev/useDevFill"; import type { Vendor } from "../accounting/VendorManagement"; import type { BomMaterial, CalculationResults } from "./types"; @@ -156,22 +157,12 @@ export function QuoteRegistrationV2({ isLoading = false, hideHeader = false, }: QuoteRegistrationV2Props) { - // --------------------------------------------------------------------------- - // 인증 정보 - // --------------------------------------------------------------------------- - const { currentUser } = useAuth(); - // --------------------------------------------------------------------------- // 상태 // --------------------------------------------------------------------------- - const [formData, setFormData] = useState(() => { - const data = initialData || INITIAL_FORM_DATA; - // create 모드에서 writer가 비어있으면 현재 사용자명으로 설정 - if (mode === "create" && !data.writer && currentUser?.name) { - return { ...data, writer: currentUser.name }; - } - return data; - }); + const [formData, setFormData] = useState( + initialData || INITIAL_FORM_DATA + ); const [selectedLocationId, setSelectedLocationId] = useState(null); const [isSaving, setIsSaving] = useState(false); const [isCalculating, setIsCalculating] = useState(false); @@ -184,6 +175,79 @@ export function QuoteRegistrationV2({ const [isLoadingClients, setIsLoadingClients] = useState(false); const [isLoadingProducts, setIsLoadingProducts] = useState(false); + // --------------------------------------------------------------------------- + // DevFill (개발/테스트용 자동 채우기) + // --------------------------------------------------------------------------- + useDevFill("quoteV2", useCallback(() => { + // 랜덤 개소 생성 함수 + const createRandomLocation = (index: number): LocationItem => { + const floors = ["B2", "B1", "1F", "2F", "3F", "4F", "5F", "R"]; + const codePrefix = ["SD", "FSS", "FD", "SS", "DS"]; + const guideRailTypes = ["wall", "floor"]; + const motorPowers = ["single", "three"]; + const controllers = ["basic", "smart", "premium"]; + + const randomFloor = floors[Math.floor(Math.random() * floors.length)]; + const randomPrefix = codePrefix[Math.floor(Math.random() * codePrefix.length)]; + const randomWidth = Math.floor(Math.random() * 4000) + 2000; // 2000~6000 + const randomHeight = Math.floor(Math.random() * 3000) + 2000; // 2000~5000 + const randomProduct = finishedGoods[Math.floor(Math.random() * finishedGoods.length)]; + + return { + id: `loc-${Date.now()}-${index}`, + floor: randomFloor, + code: `${randomPrefix}-${String(index + 1).padStart(2, "0")}`, + openWidth: randomWidth, + openHeight: randomHeight, + productCode: randomProduct?.item_code || "FG-001", + productName: randomProduct?.item_name || "방화셔터", + quantity: Math.floor(Math.random() * 3) + 1, // 1~3 + guideRailType: guideRailTypes[Math.floor(Math.random() * guideRailTypes.length)], + motorPower: motorPowers[Math.floor(Math.random() * motorPowers.length)], + controller: controllers[Math.floor(Math.random() * controllers.length)], + wingSize: [50, 60, 70][Math.floor(Math.random() * 3)], + inspectionFee: [50000, 60000, 70000][Math.floor(Math.random() * 3)], + }; + }; + + // 1~5개 랜덤 개소 생성 + const locationCount = Math.floor(Math.random() * 5) + 1; + const testLocations: LocationItem[] = []; + for (let i = 0; i < locationCount; i++) { + testLocations.push(createRandomLocation(i)); + } + + // 로그인 사용자 정보 가져오기 + let writerName = ""; + try { + const userStr = localStorage.getItem("user"); + if (userStr) { + const user = JSON.parse(userStr); + writerName = user?.name || ""; + } + } catch (e) { + console.error("[DevFill] 사용자 정보 로드 실패:", e); + } + + const testData: QuoteFormDataV2 = { + registrationDate: new Date().toISOString().split("T")[0], + writer: writerName, + clientId: clients[0]?.id?.toString() || "", + clientName: clients[0]?.company_name || "테스트 거래처", + siteName: "테스트 현장", + manager: "홍길동", + contact: "010-1234-5678", + dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split("T")[0], + remarks: "[DevFill] 테스트 견적입니다.", + status: "draft", + locations: testLocations, + }; + + setFormData(testData); + setSelectedLocationId(testLocations[0].id); + toast.success(`[DevFill] 테스트 데이터가 채워졌습니다. (${locationCount}개 개소)`); + }, [clients, finishedGoods])); + // --------------------------------------------------------------------------- // 계산된 값 // --------------------------------------------------------------------------- @@ -211,13 +275,24 @@ export function QuoteRegistrationV2({ }, [formData.locations]); // --------------------------------------------------------------------------- - // 작성자 자동 설정 (create 모드에서 currentUser 로드 시) + // 작성자 자동 설정 (create 모드에서 로그인 사용자 정보 로드) // --------------------------------------------------------------------------- useEffect(() => { - if (mode === "create" && !formData.writer && currentUser?.name) { - setFormData((prev) => ({ ...prev, writer: currentUser.name })); + if (mode === "create" && !formData.writer) { + // 실제 로그인 사용자 정보는 localStorage('user')에 저장됨 + try { + const userStr = localStorage.getItem("user"); + if (userStr) { + const user = JSON.parse(userStr); + if (user?.name) { + setFormData((prev) => ({ ...prev, writer: user.name })); + } + } + } catch (e) { + console.error("[QuoteRegistrationV2] 사용자 정보 로드 실패:", e); + } } - }, [mode, currentUser?.name, formData.writer]); + }, [mode, formData.writer]); // --------------------------------------------------------------------------- // 초기 데이터 로드