From 12b4259ebc6d380c3a18a84cee836652e7f2c514 Mon Sep 17 00:00:00 2001 From: kent Date: Fri, 9 Jan 2026 08:32:52 +0900 Subject: [PATCH] =?UTF-8?q?refactor(work-orders):=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EA=B8=B0=EB=B0=98=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 수정 내용 - 검색 debounce: WorkOrderList, SalesOrderSelectModal에 300ms debounce 적용 - 작업 버튼: 상태별 시작/완료 버튼 구현 (WorkOrderDetail) - API 경로: /sales-orders → /orders 수정 - 다중 담당자: assignees 타입 및 변환 함수 추가 - scheduledDate 필드 매핑 수정 ## 변경 파일 - WorkOrderList.tsx, SalesOrderSelectModal.tsx (debounce) - WorkOrderDetail.tsx (action buttons) - actions.ts (API path fix) - types.ts (assignees type) --- .../WorkOrders/SalesOrderSelectModal.tsx | 19 +++- .../production/WorkOrders/WorkOrderDetail.tsx | 58 ++++++++++- .../production/WorkOrders/WorkOrderList.tsx | 96 ++++++++++++------- .../production/WorkOrders/actions.ts | 6 +- src/components/production/WorkOrders/types.ts | 36 ++++++- 5 files changed, 170 insertions(+), 45 deletions(-) diff --git a/src/components/production/WorkOrders/SalesOrderSelectModal.tsx b/src/components/production/WorkOrders/SalesOrderSelectModal.tsx index 85abbe12..9820ad9c 100644 --- a/src/components/production/WorkOrders/SalesOrderSelectModal.tsx +++ b/src/components/production/WorkOrders/SalesOrderSelectModal.tsx @@ -19,6 +19,18 @@ import { toast } from 'sonner'; import { getSalesOrdersForWorkOrder } from './actions'; import type { SalesOrder } from './types'; +// Debounce 훅 +function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return debouncedValue; +} + interface SalesOrderSelectModalProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -34,12 +46,15 @@ export function SalesOrderSelectModal({ const [salesOrders, setSalesOrders] = useState([]); const [isLoading, setIsLoading] = useState(false); + // 디바운스된 검색어 (300ms 딜레이) + const debouncedSearchTerm = useDebounce(searchTerm, 300); + // API로 수주 목록 로드 const loadSalesOrders = useCallback(async () => { setIsLoading(true); try { const result = await getSalesOrdersForWorkOrder({ - q: searchTerm || undefined, + q: debouncedSearchTerm || undefined, }); if (result.success) { // API 응답을 SalesOrder 타입으로 변환 @@ -63,7 +78,7 @@ export function SalesOrderSelectModal({ } finally { setIsLoading(false); } - }, [searchTerm]); + }, [debouncedSearchTerm]); // 모달이 열릴 때 데이터 로드 useEffect(() => { diff --git a/src/components/production/WorkOrders/WorkOrderDetail.tsx b/src/components/production/WorkOrders/WorkOrderDetail.tsx index c4edb024..e5e61db9 100644 --- a/src/components/production/WorkOrders/WorkOrderDetail.tsx +++ b/src/components/production/WorkOrders/WorkOrderDetail.tsx @@ -21,7 +21,7 @@ import { import { PageLayout } from '@/components/organisms/PageLayout'; import { WorkLogModal } from '../WorkerScreen/WorkLogModal'; import { toast } from 'sonner'; -import { getWorkOrderById } from './actions'; +import { getWorkOrderById, updateWorkOrderStatus } from './actions'; import { PROCESS_TYPE_LABELS, WORK_ORDER_STATUS_LABELS, @@ -191,6 +191,7 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) { const [isWorkLogOpen, setIsWorkLogOpen] = useState(false); const [order, setOrder] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [isStatusUpdating, setIsStatusUpdating] = useState(false); // API에서 데이터 로드 const loadData = useCallback(async () => { @@ -214,6 +215,32 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) { loadData(); }, [loadData]); + // 상태 변경 핸들러 + const handleStatusChange = useCallback(async (newStatus: 'waiting' | 'in_progress' | 'completed') => { + if (!order) return; + + setIsStatusUpdating(true); + try { + const result = await updateWorkOrderStatus(orderId, newStatus); + if (result.success && result.data) { + setOrder(result.data); + const statusLabels = { + waiting: '작업대기', + in_progress: '작업중', + completed: '작업완료', + }; + toast.success(`상태가 '${statusLabels[newStatus]}'(으)로 변경되었습니다.`); + } else { + toast.error(result.error || '상태 변경에 실패했습니다.'); + } + } catch (error) { + console.error('[WorkOrderDetail] handleStatusChange error:', error); + toast.error('상태 변경 중 오류가 발생했습니다.'); + } finally { + setIsStatusUpdating(false); + } + }, [order, orderId]); + // 로딩 상태 if (isLoading) { return ( @@ -260,6 +287,35 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {

작업지시 상세

+ {/* 상태 변경 버튼 */} + {order.status === 'waiting' && ( + + )} + {order.status === 'in_progress' && ( + + )}