feat: 수주/견적 기능 개선 및 PDF 생성 업데이트

- 수주 상세 뷰/수정 컴포넌트 개선
- 견적 위치 패널 업데이트
- PDF 생성 API 수정
- 레이아웃 및 공통코드 API 업데이트
- 패키지 의존성 업데이트
This commit is contained in:
2026-01-29 01:12:58 +09:00
parent d2a39de576
commit 6bcd298995
12 changed files with 272 additions and 81 deletions

View File

@@ -45,6 +45,7 @@ import {
updateOrder,
type OrderStatus,
} from "@/components/orders";
import { getDeliveryMethodOptions, getCommonCodeOptions } from "@/lib/api/common-codes";
// 수정 폼 데이터
interface EditFormData {
@@ -88,22 +89,11 @@ interface EditFormData {
}>;
}
// 배송방식 옵션
const DELIVERY_METHODS = [
{ value: "direct", label: "직접배차" },
{ value: "pickup", label: "상차" },
{ value: "courier", label: "택배" },
{ value: "self", label: "직접수령" },
{ value: "freight", label: "화물" },
];
// 운임비용 옵션
const SHIPPING_COSTS = [
{ value: "free", label: "무료" },
{ value: "prepaid", label: "선불" },
{ value: "collect", label: "착불" },
{ value: "negotiable", label: "협의" },
];
// 옵션 타입 정의
interface SelectOption {
value: string;
label: string;
}
// 상태 뱃지 헬퍼
@@ -141,6 +131,10 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
const [isSaving, setIsSaving] = useState(false);
const [expandedProducts, setExpandedProducts] = useState<Set<string>>(new Set());
// 공통코드 옵션
const [deliveryMethods, setDeliveryMethods] = useState<SelectOption[]>([]);
const [shippingCosts, setShippingCosts] = useState<SelectOption[]>([]);
// 제품-부품 트리 토글
const toggleProduct = (key: string) => {
setExpandedProducts((prev) => {
@@ -265,6 +259,24 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
loadOrder();
}, [orderId, router]);
// 공통코드 옵션 로드
useEffect(() => {
async function loadCommonCodes() {
const [deliveryResult, shippingResult] = await Promise.all([
getDeliveryMethodOptions(),
getCommonCodeOptions('shipping_cost'),
]);
if (deliveryResult.success && deliveryResult.data) {
setDeliveryMethods(deliveryResult.data);
}
if (shippingResult.success && shippingResult.data) {
setShippingCosts(shippingResult.data);
}
}
loadCommonCodes();
}, []);
const handleCancel = () => {
// V2 패턴: ?mode=view로 이동
router.push(`/sales/order-management-sales/${orderId}?mode=view`);
@@ -453,7 +465,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{DELIVERY_METHODS.map((method) => (
{deliveryMethods.map((method) => (
<SelectItem key={method.value} value={method.value}>
{method.label}
</SelectItem>
@@ -476,7 +488,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{SHIPPING_COSTS.map((cost) => (
{shippingCosts.map((cost) => (
<SelectItem key={cost.value} value={cost.value}>
{cost.label}
</SelectItem>

View File

@@ -354,7 +354,7 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
<InfoItem label="출고예정일" value={order.expectedShipDate || "미정"} />
<InfoItem label="납품요청일" value={order.deliveryRequestDate} />
<InfoItem label="배송방식" value={order.deliveryMethodLabel} />
<InfoItem label="운임비용" value={order.shippingCost} />
<InfoItem label="운임비용" value={order.shippingCostLabel} />
<InfoItem label="수신(반장/업체)" value={order.receiver} />
<InfoItem label="수신처 연락처" value={order.receiverContact} />
<InfoItem label="수신처 주소" value={order.address} />

View File

@@ -27,6 +27,7 @@ interface ApiOrder {
delivery_date: string | null;
delivery_method_code: string | null;
delivery_method_label?: string; // API에서 조회한 배송방식 라벨
shipping_cost_label?: string; // API에서 조회한 운임비용 라벨
received_at: string | null;
memo: string | null;
remarks: string | null;
@@ -231,11 +232,17 @@ export interface Order {
remarks?: string;
note?: string;
items?: OrderItem[];
// 목록 페이지용 추가 필드
productName?: string; // 제품명 (첫 번째 품목명)
receiverAddress?: string; // 수신주소
receiverPlace?: string; // 수신처 (전화번호)
frameCount?: number; // 틀수 (수량)
// 상세 페이지용 추가 필드
manager?: string; // 담당자
contact?: string; // 연락처 (client_contact)
deliveryRequestDate?: string; // 납품요청일
shippingCost?: string; // 운임비용
shippingCost?: string; // 운임비용 (코드)
shippingCostLabel?: string; // 운임비용 (라벨)
receiver?: string; // 수신자
receiverContact?: string; // 수신처 연락처
address?: string; // 수신처 주소
@@ -457,12 +464,19 @@ function transformApiToFrontend(apiData: ApiOrder): Order {
memo: apiData.memo ?? undefined,
remarks: apiData.remarks ?? undefined,
note: apiData.note ?? undefined,
items: apiData.items?.map(transformItemApiToFrontend) || [], // 상세 페이지용 추가 필드 (API에서 매핑)
items: apiData.items?.map(transformItemApiToFrontend) || [],
// 목록 페이지용 추가 필드
productName: apiData.items?.[0]?.item_name ?? undefined,
receiverAddress: apiData.options?.shipping_address ?? undefined,
receiverPlace: apiData.options?.receiver_contact ?? undefined,
frameCount: apiData.quantity ?? undefined,
// 상세 페이지용 추가 필드 (API에서 매핑)
manager: apiData.client?.manager_name ?? undefined,
contact: apiData.client_contact ?? apiData.client?.phone ?? undefined,
deliveryRequestDate: apiData.delivery_date ?? undefined, // delivery_date를 공유
// options JSON에서 추출
shippingCost: apiData.options?.shipping_cost_code ?? undefined,
shippingCostLabel: apiData.shipping_cost_label ?? undefined,
receiver: apiData.options?.receiver ?? undefined,
receiverContact: apiData.options?.receiver_contact ?? undefined,
address: apiData.options?.shipping_address ?? undefined,