feat(WEB): DevToolbar - 견적→수주→작업지시→출하 테스트 자동화 도구

- DevFillContext: 전역 상태 관리 (활성화/페이지 타입/폼 채우기 함수)
- DevToolbar: 플로팅 UI 컴포넌트 (토글/자동 채우기 버튼)
- useDevFill: 각 폼에서 자동 채우기 함수 등록 커스텀 훅
- 데이터 생성기: 견적/수주/작업지시/출하 샘플 데이터
- 환경변수 제어: NEXT_PUBLIC_DEV_TOOLBAR_ENABLED로 On/Off
- 통합: QuoteRegistration, OrderRegistration, WorkOrderCreate, ShipmentCreate
- Hydration 불일치 방지: useState 초기값 false + useEffect 패턴
This commit is contained in:
2026-01-20 20:38:29 +09:00
parent c101b8bf7e
commit eae23d4457
15 changed files with 1048 additions and 5 deletions

View File

@@ -8,7 +8,7 @@
"use client";
import { useState, useEffect, useMemo } from "react";
import { useState, useEffect, useMemo, useCallback } from "react";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
import {
@@ -55,6 +55,8 @@ import { getClients } from "../accounting/VendorManagement/actions";
import { isNextRedirectError } from "@/lib/utils/redirect-error";
import type { Vendor } from "../accounting/VendorManagement";
import type { BomMaterial, CalculationResults } from "./types";
import { useDevFill } from "@/components/dev";
import { generateQuoteData } from "@/components/dev/generators/quoteData";
// 견적 항목 타입
export interface QuoteItem {
@@ -197,6 +199,20 @@ export function QuoteRegistration({
// 현장명 자동완성 목록 상태
const [siteNames, setSiteNames] = useState<string[]>([]);
// DevToolbar용 폼 자동 채우기 등록
useDevFill(
'quote',
useCallback(() => {
// 실제 로드된 데이터를 기반으로 샘플 데이터 생성
const sampleData = generateQuoteData({
clients: clients.map(c => ({ id: c.id, name: c.vendorName })),
products: finishedGoods.map(p => ({ code: p.item_code, name: p.item_name, category: p.category })),
});
setFormData(sampleData);
toast.success('[Dev] 견적 폼이 자동으로 채워졌습니다.');
}, [clients, finishedGoods])
);
// 수량 반영 총합계 계산 (useMemo로 최적화)
const calculatedGrandTotal = useMemo(() => {
if (!calculationResults?.items) return 0;
@@ -1103,7 +1119,7 @@ export function QuoteRegistration({
</div>
<div className="text-right">
<div className="text-sm text-muted-foreground">
: {itemResult.result.grand_total.toLocaleString()}
: {(itemResult.result.grand_total || 0).toLocaleString()}
</div>
<div className="font-semibold text-green-700">
: {((itemResult.result.grand_total || 0) * (formItem?.quantity || 1)).toLocaleString()}