feat(WEB): 자재/영업/품질 모듈 기능 개선 및 문서 컴포넌트 추가
- 입고관리: 상세/목록 UI 개선, actions 로직 강화 - 재고현황: 상세/목록 개선, StockAuditModal 신규 추가 - 영업주문관리: 페이지 구조 개선, OrderSalesDetailEdit 기능 강화 - 주문: OrderRegistration 개선, SalesOrderDocument 신규 추가 - 견적: QuoteTransactionModal 기능 개선 - 품질: InspectionModalV2, ImportInspectionDocument 대폭 개선 - UniversalListPage: 템플릿 기능 확장 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,7 @@
|
||||
* IntegratedDetailTemplate 마이그레이션 (2026-01-20)
|
||||
* - 견적 불러오기 섹션
|
||||
* - 기본 정보 섹션
|
||||
* - 수주/배송 정보 섹션
|
||||
* - 수신처 주소 섹션
|
||||
* - 수주/배송 정보 섹션 (주소 포함)
|
||||
* - 비고 섹션
|
||||
* - 품목 내역 섹션
|
||||
*/
|
||||
@@ -46,8 +45,6 @@ import {
|
||||
X,
|
||||
Plus,
|
||||
Trash2,
|
||||
Info,
|
||||
MapPin,
|
||||
Truck,
|
||||
Package,
|
||||
MessageSquare,
|
||||
@@ -133,6 +130,8 @@ const DELIVERY_METHODS = [
|
||||
{ value: "direct", label: "직접배차" },
|
||||
{ value: "pickup", label: "상차" },
|
||||
{ value: "courier", label: "택배" },
|
||||
{ value: "self", label: "직접수령" },
|
||||
{ value: "freight", label: "화물" },
|
||||
];
|
||||
|
||||
// 운임비용 옵션
|
||||
@@ -162,11 +161,11 @@ interface FieldErrors {
|
||||
|
||||
// 필드명 한글 매핑
|
||||
const FIELD_NAME_MAP: Record<string, string> = {
|
||||
clientName: "발주처",
|
||||
clientName: "수주처",
|
||||
siteName: "현장명",
|
||||
deliveryRequestDate: "납품요청일",
|
||||
receiver: "수신(반장/업체)",
|
||||
receiverContact: "수신처 연락처",
|
||||
receiver: "수신자",
|
||||
receiverContact: "수신처",
|
||||
items: "품목 내역",
|
||||
};
|
||||
|
||||
@@ -456,13 +455,34 @@ export function OrderRegistration({
|
||||
{/* 기본 정보 섹션 */}
|
||||
<FormSection
|
||||
title="기본 정보"
|
||||
description="발주처 및 현장 정보를 입력하세요"
|
||||
description="수주처 및 현장 정보를 입력하세요"
|
||||
icon={FileText}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{/* 첫 번째 줄: 로트번호, 접수일, 수주처, 현장명 */}
|
||||
<div className="space-y-2">
|
||||
<Label>로트번호</Label>
|
||||
<Input
|
||||
value=""
|
||||
disabled
|
||||
className="bg-muted"
|
||||
placeholder="자동 생성"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>접수일</Label>
|
||||
<Input
|
||||
value=""
|
||||
disabled
|
||||
className="bg-muted"
|
||||
placeholder="자동 생성"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
발주처 <span className="text-red-500">*</span>
|
||||
수주처 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={form.clientId}
|
||||
@@ -478,8 +498,8 @@ export function OrderRegistration({
|
||||
disabled={!!form.selectedQuotation || isClientsLoading}
|
||||
>
|
||||
<SelectTrigger className={cn(fieldErrors.clientName && "border-red-500")}>
|
||||
<SelectValue placeholder={isClientsLoading ? "불러오는 중..." : "발주처 선택"}>
|
||||
{form.clientName || (isClientsLoading ? "불러오는 중..." : "발주처 선택")}
|
||||
<SelectValue placeholder={isClientsLoading ? "불러오는 중..." : "수주처 선택"}>
|
||||
{form.clientName || (isClientsLoading ? "불러오는 중..." : "수주처 선택")}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -514,6 +534,7 @@ export function OrderRegistration({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 두 번째 줄: 담당자, 연락처, 상태 */}
|
||||
<div className="space-y-2">
|
||||
<Label>담당자</Label>
|
||||
<Input
|
||||
@@ -535,6 +556,16 @@ export function OrderRegistration({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>상태</Label>
|
||||
<Input
|
||||
value=""
|
||||
disabled
|
||||
className="bg-muted"
|
||||
placeholder="자동 생성"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
@@ -544,93 +575,55 @@ export function OrderRegistration({
|
||||
description="출고 및 배송 정보를 입력하세요"
|
||||
icon={Truck}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* 출고예정일 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{/* 첫 번째 줄: 수주일, 납품요청일, 출고예정일, 배송방식 */}
|
||||
<div className="space-y-2">
|
||||
<Label>출고예정일</Label>
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
type="date"
|
||||
value={form.expectedShipDate}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
expectedShipDate: e.target.value,
|
||||
}))
|
||||
}
|
||||
disabled={form.expectedShipDateUndecided}
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="expectedShipDateUndecided"
|
||||
checked={form.expectedShipDateUndecided}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
expectedShipDateUndecided: checked as boolean,
|
||||
expectedShipDate: checked ? "" : prev.expectedShipDate,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="expectedShipDateUndecided"
|
||||
className="text-sm font-normal"
|
||||
>
|
||||
미정
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Label>수주일</Label>
|
||||
<Input
|
||||
value=""
|
||||
disabled
|
||||
className="bg-muted"
|
||||
placeholder="자동 생성"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 납품요청일 */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
납품요청일 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
type="date"
|
||||
value={form.deliveryRequestDate}
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
deliveryRequestDate: e.target.value,
|
||||
}));
|
||||
clearFieldError("deliveryRequestDate");
|
||||
}}
|
||||
disabled={form.deliveryRequestDateUndecided}
|
||||
className={cn("flex-1", fieldErrors.deliveryRequestDate && "border-red-500")}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="deliveryRequestDateUndecided"
|
||||
checked={form.deliveryRequestDateUndecided}
|
||||
onCheckedChange={(checked) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
deliveryRequestDateUndecided: checked as boolean,
|
||||
deliveryRequestDate: checked
|
||||
? ""
|
||||
: prev.deliveryRequestDate,
|
||||
}));
|
||||
if (checked) clearFieldError("deliveryRequestDate");
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="deliveryRequestDateUndecided"
|
||||
className="text-sm font-normal"
|
||||
>
|
||||
미정
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
type="date"
|
||||
value={form.deliveryRequestDate}
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
deliveryRequestDate: e.target.value,
|
||||
}));
|
||||
clearFieldError("deliveryRequestDate");
|
||||
}}
|
||||
disabled={form.deliveryRequestDateUndecided}
|
||||
className={cn(fieldErrors.deliveryRequestDate && "border-red-500")}
|
||||
/>
|
||||
{fieldErrors.deliveryRequestDate && (
|
||||
<p className="text-sm text-red-500">{fieldErrors.deliveryRequestDate}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 배송방식 */}
|
||||
<div className="space-y-2">
|
||||
<Label>출고예정일</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={form.expectedShipDate}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
expectedShipDate: e.target.value,
|
||||
}))
|
||||
}
|
||||
disabled={form.expectedShipDateUndecided}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>배송방식</Label>
|
||||
<Select
|
||||
@@ -640,7 +633,7 @@ export function OrderRegistration({
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="배송방식 선택" />
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{DELIVERY_METHODS.map((method) => (
|
||||
@@ -652,7 +645,7 @@ export function OrderRegistration({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 운임비용 */}
|
||||
{/* 두 번째 줄: 운임비용, 수신자, 수신처 */}
|
||||
<div className="space-y-2">
|
||||
<Label>운임비용</Label>
|
||||
<Select
|
||||
@@ -662,7 +655,7 @@ export function OrderRegistration({
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="운임비용 선택" />
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SHIPPING_COSTS.map((cost) => (
|
||||
@@ -674,10 +667,9 @@ export function OrderRegistration({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 수신(반장/업체) */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
수신(반장/업체) <span className="text-red-500">*</span>
|
||||
수신자 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="수신자명 입력"
|
||||
@@ -693,18 +685,17 @@ export function OrderRegistration({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 수신처 연락처 */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
수신처 연락처 <span className="text-red-500">*</span>
|
||||
수신처 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<PhoneInput
|
||||
placeholder="010-0000-0000"
|
||||
<Input
|
||||
placeholder="수신처 입력"
|
||||
value={form.receiverContact}
|
||||
onChange={(value) => {
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
receiverContact: value,
|
||||
receiverContact: e.target.value,
|
||||
}));
|
||||
clearFieldError("receiverContact");
|
||||
}}
|
||||
@@ -714,43 +705,32 @@ export function OrderRegistration({
|
||||
<p className="text-sm text-red-500">{fieldErrors.receiverContact}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
{/* 수신처 주소 섹션 */}
|
||||
<FormSection
|
||||
title="수신처 주소"
|
||||
description="배송지 주소를 입력하세요"
|
||||
icon={MapPin}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="우편번호"
|
||||
value={form.zipCode}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, zipCode: e.target.value }))
|
||||
}
|
||||
className="w-32"
|
||||
/>
|
||||
<Button variant="outline" type="button" onClick={openPostcode}>
|
||||
우편번호 찾기
|
||||
</Button>
|
||||
{/* 주소 - 전체 너비 사용 */}
|
||||
<div className="space-y-2 md:col-span-4">
|
||||
<Label>주소</Label>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" type="button" onClick={openPostcode}>
|
||||
우편번호 찾기
|
||||
</Button>
|
||||
<Input
|
||||
placeholder="우편번호"
|
||||
value={form.zipCode}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, zipCode: e.target.value }))
|
||||
}
|
||||
className="w-32"
|
||||
/>
|
||||
<Input
|
||||
placeholder="주소"
|
||||
value={form.address}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, address: e.target.value }))
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="기본 주소"
|
||||
value={form.address}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, address: e.target.value }))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="상세 주소 입력"
|
||||
value={form.addressDetail}
|
||||
onChange={(e) =>
|
||||
setForm((prev) => ({ ...prev, addressDetail: e.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { AlertTriangle, ChevronDown, ChevronRight, ChevronsUpDown, Package } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate";
|
||||
import { orderSalesConfig } from "./orderSalesConfig";
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
interface EditFormData {
|
||||
// 읽기전용 정보
|
||||
lotNumber: string;
|
||||
orderDate: string; // 접수일
|
||||
quoteNumber: string;
|
||||
client: string;
|
||||
siteName: string;
|
||||
@@ -75,6 +76,16 @@ interface EditFormData {
|
||||
subtotal: number;
|
||||
discountRate: number;
|
||||
totalAmount: number;
|
||||
// 제품 정보 (아코디언용)
|
||||
products: Array<{
|
||||
productName: string;
|
||||
productCategory?: string;
|
||||
openWidth?: string;
|
||||
openHeight?: string;
|
||||
quantity: number;
|
||||
floor?: string;
|
||||
code?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// 배송방식 옵션
|
||||
@@ -82,6 +93,8 @@ const DELIVERY_METHODS = [
|
||||
{ value: "direct", label: "직접배차" },
|
||||
{ value: "pickup", label: "상차" },
|
||||
{ value: "courier", label: "택배" },
|
||||
{ value: "self", label: "직접수령" },
|
||||
{ value: "freight", label: "화물" },
|
||||
];
|
||||
|
||||
// 운임비용 옵션
|
||||
@@ -126,6 +139,77 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
const [form, setForm] = useState<EditFormData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [expandedProducts, setExpandedProducts] = useState<Set<string>>(new Set());
|
||||
|
||||
// 제품-부품 트리 토글
|
||||
const toggleProduct = (key: string) => {
|
||||
setExpandedProducts((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(key)) {
|
||||
next.delete(key);
|
||||
} else {
|
||||
next.add(key);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
// 모든 제품 확장
|
||||
const expandAllProducts = () => {
|
||||
if (form?.products) {
|
||||
const allKeys = form.products.map((p) => `${p.floor || ""}-${p.code || ""}`);
|
||||
allKeys.push("other-parts"); // 기타부품도 포함
|
||||
setExpandedProducts(new Set(allKeys));
|
||||
}
|
||||
};
|
||||
|
||||
// 모든 제품 축소
|
||||
const collapseAllProducts = () => {
|
||||
setExpandedProducts(new Set());
|
||||
};
|
||||
|
||||
// 제품별로 부품 그룹화 (floor_code, symbol_code 매칭)
|
||||
const getItemsForProduct = (floor: string | undefined, code: string | undefined) => {
|
||||
if (!form?.items) return [];
|
||||
return form.items.filter((item) => {
|
||||
const itemFloor = item.type || "";
|
||||
const itemSymbol = item.symbol || "";
|
||||
const productFloor = floor || "";
|
||||
const productCode = code || "";
|
||||
return itemFloor === productFloor && itemSymbol === productCode;
|
||||
});
|
||||
};
|
||||
|
||||
// 매칭되지 않은 부품 (orphan items)
|
||||
const getUnmatchedItems = () => {
|
||||
if (!form?.items || !form?.products) return form?.items || [];
|
||||
const matchedIds = new Set<string>();
|
||||
form.products.forEach((product) => {
|
||||
const items = getItemsForProduct(product.floor, product.code);
|
||||
items.forEach((item) => matchedIds.add(item.id));
|
||||
});
|
||||
return form.items.filter((item) => !matchedIds.has(item.id));
|
||||
};
|
||||
|
||||
/**
|
||||
* 수량 포맷 함수
|
||||
* - EA, SET, PCS 등 개수 단위: 정수로 표시
|
||||
* - M, M2, KG, L 등 측정 단위: 소수점 이하 불필요한 0 제거
|
||||
*/
|
||||
const formatQuantity = (quantity: number, unit?: string): string => {
|
||||
const countableUnits = ["EA", "SET", "PCS", "개", "세트", "BOX", "ROLL"];
|
||||
const upperUnit = (unit || "").toUpperCase();
|
||||
|
||||
if (countableUnits.includes(upperUnit)) {
|
||||
return Math.round(quantity).toLocaleString();
|
||||
}
|
||||
|
||||
const rounded = Math.round(quantity * 10000) / 10000;
|
||||
return rounded.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 4
|
||||
});
|
||||
};
|
||||
|
||||
// 데이터 로드 (API)
|
||||
useEffect(() => {
|
||||
@@ -142,6 +226,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
// Order 데이터를 EditFormData로 변환
|
||||
setForm({
|
||||
lotNumber: order.lotNumber,
|
||||
orderDate: order.orderDate || "",
|
||||
quoteNumber: order.quoteNumber || "",
|
||||
client: order.client,
|
||||
siteName: order.siteName,
|
||||
@@ -163,6 +248,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
subtotal: order.subtotal || order.amount,
|
||||
discountRate: order.discountRate || 0,
|
||||
totalAmount: order.amount,
|
||||
products: order.products || [],
|
||||
});
|
||||
} else {
|
||||
toast.error(result.error || "수주 정보를 불러오는데 실패했습니다.");
|
||||
@@ -193,10 +279,10 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
return { success: false, error: "납품요청일을 입력해주세요." };
|
||||
}
|
||||
if (!form.receiver.trim()) {
|
||||
return { success: false, error: "수신(반장/업체)을 입력해주세요." };
|
||||
return { success: false, error: "수신자를 입력해주세요." };
|
||||
}
|
||||
if (!form.receiverContact.trim()) {
|
||||
return { success: false, error: "수신처 연락처를 입력해주세요." };
|
||||
return { success: false, error: "수신처를 입력해주세요." };
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -279,30 +365,34 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">로트번호</Label>
|
||||
<p className="font-medium">{form.lotNumber}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">견적번호</Label>
|
||||
<p className="font-medium">{form.quoteNumber}</p>
|
||||
<Label className="text-muted-foreground text-sm">접수일</Label>
|
||||
<p className="font-medium">{form.orderDate || "-"}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">담당자</Label>
|
||||
<p className="font-medium">{form.manager}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">발주처</Label>
|
||||
<Label className="text-muted-foreground text-sm">수주처</Label>
|
||||
<p className="font-medium">{form.client}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">현장명</Label>
|
||||
<p className="font-medium">{form.siteName}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">담당자</Label>
|
||||
<p className="font-medium">{form.manager || "-"}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">연락처</Label>
|
||||
<p className="font-medium">{form.contact}</p>
|
||||
<p className="font-medium">{form.contact || "-"}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-sm">상태</Label>
|
||||
<div className="mt-1">{getOrderStatusBadge(form.status)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -314,43 +404,17 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
<CardTitle className="text-lg">수주/배송 정보</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* 출고예정일 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{/* 첫 번째 줄: 수주일, 납품요청일, 출고예정일, 배송방식 */}
|
||||
<div className="space-y-2">
|
||||
<Label>출고예정일</Label>
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
type="date"
|
||||
value={form.expectedShipDate}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, expectedShipDate: e.target.value })
|
||||
}
|
||||
disabled={form.expectedShipDateUndecided}
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="expectedShipDateUndecided"
|
||||
checked={form.expectedShipDateUndecided}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm({
|
||||
...form,
|
||||
expectedShipDateUndecided: checked as boolean,
|
||||
expectedShipDate: checked ? "" : form.expectedShipDate,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="expectedShipDateUndecided"
|
||||
className="text-sm font-normal"
|
||||
>
|
||||
미정
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Label className="text-muted-foreground">수주일</Label>
|
||||
<Input
|
||||
value={form.orderDate || ""}
|
||||
disabled
|
||||
className="bg-muted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 납품요청일 */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
납품요청일 <span className="text-red-500">*</span>
|
||||
@@ -364,7 +428,18 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 배송방식 */}
|
||||
<div className="space-y-2">
|
||||
<Label>출고예정일</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={form.expectedShipDate}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, expectedShipDate: e.target.value })
|
||||
}
|
||||
disabled={form.expectedShipDateUndecided}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>배송방식</Label>
|
||||
<Select
|
||||
@@ -375,7 +450,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="배송방식 선택" />
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{DELIVERY_METHODS.map((method) => (
|
||||
@@ -387,7 +462,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 운임비용 */}
|
||||
{/* 두 번째 줄: 운임비용, 수신자, 수신처 */}
|
||||
<div className="space-y-2">
|
||||
<Label>운임비용</Label>
|
||||
<Select
|
||||
@@ -398,7 +473,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="운임비용 선택" />
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SHIPPING_COSTS.map((cost) => (
|
||||
@@ -410,50 +485,45 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 수신(반장/업체) */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
수신(반장/업체) <span className="text-red-500">*</span>
|
||||
수신자 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={form.receiver}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, receiver: e.target.value })
|
||||
}
|
||||
placeholder="수신자명 입력"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 수신처 연락처 */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
수신처 연락처 <span className="text-red-500">*</span>
|
||||
수신처 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<PhoneInput
|
||||
<Input
|
||||
value={form.receiverContact}
|
||||
onChange={(value) =>
|
||||
setForm({ ...form, receiverContact: value })
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, receiverContact: e.target.value })
|
||||
}
|
||||
placeholder="수신처 입력"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 수신처 주소 */}
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<Label>수신처 주소</Label>
|
||||
<Input
|
||||
value={form.address}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, address: e.target.value })
|
||||
}
|
||||
placeholder="주소"
|
||||
className="mb-2"
|
||||
/>
|
||||
<Input
|
||||
value={form.addressDetail}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, addressDetail: e.target.value })
|
||||
}
|
||||
placeholder="상세주소"
|
||||
/>
|
||||
{/* 주소 - 전체 너비 */}
|
||||
<div className="space-y-2 md:col-span-4">
|
||||
<Label>주소</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={form.address}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, address: e.target.value })
|
||||
}
|
||||
placeholder="주소"
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -474,84 +544,182 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 품목 내역 */}
|
||||
{/* 제품내용 (아코디언) */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
품목 내역
|
||||
{!form.canEditItems && (
|
||||
<span className="flex items-center gap-1 text-sm font-normal text-orange-600">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
생산 시작 후 수정 불가
|
||||
</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
제품내용
|
||||
{!form.canEditItems && (
|
||||
<span className="flex items-center gap-1 text-sm font-normal text-orange-600">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
생산 시작 후 수정 불가
|
||||
</span>
|
||||
)}
|
||||
</CardTitle>
|
||||
{form.products && form.products.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={expandAllProducts}
|
||||
className="text-xs text-gray-500 hover:text-gray-700 flex items-center gap-1"
|
||||
>
|
||||
<ChevronsUpDown className="h-3 w-3" />
|
||||
모두 펼치기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={collapseAllProducts}
|
||||
className="text-xs text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
모두 접기
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[60px] text-center">No</TableHead>
|
||||
<TableHead>품목코드</TableHead>
|
||||
<TableHead>품명</TableHead>
|
||||
<TableHead>종</TableHead>
|
||||
<TableHead>부호</TableHead>
|
||||
<TableHead>규격(mm)</TableHead>
|
||||
<TableHead className="text-center">수량</TableHead>
|
||||
<TableHead className="text-center">단위</TableHead>
|
||||
<TableHead className="text-right">단가</TableHead>
|
||||
<TableHead className="text-right">금액</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{form.items.map((item, index) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="text-center">{index + 1}</TableCell>
|
||||
<TableCell>
|
||||
<code className="text-xs bg-gray-100 px-1.5 py-0.5 rounded">
|
||||
{item.itemCode}
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell>{item.itemName}</TableCell>
|
||||
<TableCell>{item.type || "-"}</TableCell>
|
||||
<TableCell>{item.symbol || "-"}</TableCell>
|
||||
<TableCell>{item.spec}</TableCell>
|
||||
<TableCell className="text-center">{item.quantity}</TableCell>
|
||||
<TableCell className="text-center">{item.unit}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatAmount(item.unitPrice)}원
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium">
|
||||
{formatAmount(item.amount)}원
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{form.products && form.products.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{form.products.map((product, productIndex) => {
|
||||
const productKey = `${product.floor || ""}-${product.code || ""}`;
|
||||
const isExpanded = expandedProducts.has(productKey);
|
||||
const productItems = getItemsForProduct(product.floor, product.code);
|
||||
|
||||
{/* 합계 */}
|
||||
<div className="flex flex-col items-end gap-2 pt-4 mt-4 border-t">
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="text-muted-foreground">소계:</span>
|
||||
<span className="w-32 text-right">
|
||||
{formatAmount(form.subtotal)}원
|
||||
</span>
|
||||
return (
|
||||
<div
|
||||
key={productIndex}
|
||||
className="border border-gray-200 rounded-lg overflow-hidden"
|
||||
>
|
||||
{/* 제품 헤더 (클릭하면 확장/축소) */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleProduct(productKey)}
|
||||
className="w-full flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="h-5 w-5 text-gray-500" />
|
||||
) : (
|
||||
<ChevronRight className="h-5 w-5 text-gray-500" />
|
||||
)}
|
||||
<Package className="h-5 w-5 text-blue-600" />
|
||||
<div>
|
||||
<span className="font-medium">{product.productName}</span>
|
||||
{product.openWidth && product.openHeight && (
|
||||
<span className="ml-2 text-sm text-gray-500">
|
||||
({product.openWidth} × {product.openHeight})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-500">
|
||||
{productItems.length}개 부품
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* 부품 목록 (확장 시 표시) */}
|
||||
{isExpanded && (
|
||||
<div className="border-t">
|
||||
{productItems.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[60px] text-center">순번</TableHead>
|
||||
<TableHead>품목명</TableHead>
|
||||
<TableHead>규격</TableHead>
|
||||
<TableHead className="text-center">수량</TableHead>
|
||||
<TableHead className="text-center">단위</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{productItems.map((item, index) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="text-center">{index + 1}</TableCell>
|
||||
<TableCell>{item.itemName}</TableCell>
|
||||
<TableCell>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="text-center">{formatQuantity(item.quantity, item.unit)}</TableCell>
|
||||
<TableCell className="text-center">{item.unit || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="p-4 text-center text-gray-400 text-sm">
|
||||
연결된 부품이 없습니다
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="text-muted-foreground">할인율:</span>
|
||||
<span className="w-32 text-right">{form.discountRate}%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-lg font-semibold">
|
||||
<span>총금액:</span>
|
||||
<span className="w-32 text-right text-green-600">
|
||||
{formatAmount(form.totalAmount)}원
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 기타부품 (아코디언) */}
|
||||
{(() => {
|
||||
const unmatchedItems = getUnmatchedItems();
|
||||
if (unmatchedItems.length === 0) return null;
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">기타부품</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleProduct("other-parts")}
|
||||
className="w-full flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{expandedProducts.has("other-parts") ? (
|
||||
<ChevronDown className="h-5 w-5 text-gray-500" />
|
||||
) : (
|
||||
<ChevronRight className="h-5 w-5 text-gray-500" />
|
||||
)}
|
||||
<Package className="h-5 w-5 text-gray-400" />
|
||||
<span className="font-medium text-gray-600">기타부품</span>
|
||||
</div>
|
||||
<span className="text-sm text-gray-500">
|
||||
{unmatchedItems.length}개
|
||||
</span>
|
||||
</button>
|
||||
{expandedProducts.has("other-parts") && (
|
||||
<div className="border-t">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[60px] text-center">순번</TableHead>
|
||||
<TableHead>품목명</TableHead>
|
||||
<TableHead>규격</TableHead>
|
||||
<TableHead className="text-center">수량</TableHead>
|
||||
<TableHead className="text-center">단위</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{unmatchedItems.map((item, index) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="text-center">{index + 1}</TableCell>
|
||||
<TableCell>{item.itemName}</TableCell>
|
||||
<TableCell>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="text-center">{formatQuantity(item.quantity, item.unit)}</TableCell>
|
||||
<TableCell className="text-center">{item.unit || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})()}
|
||||
|
||||
</div>
|
||||
);
|
||||
}, [form]);
|
||||
|
||||
@@ -11,11 +11,12 @@ import { DocumentViewer } from "@/components/document-system";
|
||||
import { ContractDocument } from "./ContractDocument";
|
||||
import { TransactionDocument } from "./TransactionDocument";
|
||||
import { PurchaseOrderDocument } from "./PurchaseOrderDocument";
|
||||
import { SalesOrderDocument } from "./SalesOrderDocument";
|
||||
import { OrderItem } from "../actions";
|
||||
import { getCompanyInfo } from "@/components/settings/CompanyInfoManagement/actions";
|
||||
|
||||
// 문서 타입
|
||||
export type OrderDocumentType = "contract" | "transaction" | "purchaseOrder";
|
||||
export type OrderDocumentType = "contract" | "transaction" | "purchaseOrder" | "salesOrder";
|
||||
|
||||
// 제품 정보 타입 (견적의 calculation_inputs에서 추출)
|
||||
export interface ProductInfo {
|
||||
@@ -46,6 +47,13 @@ export interface OrderDocumentData {
|
||||
discountRate?: number;
|
||||
totalAmount?: number;
|
||||
remarks?: string;
|
||||
// 수주서 전용 필드
|
||||
documentNumber?: string;
|
||||
certificationNumber?: string;
|
||||
recipientName?: string;
|
||||
recipientContact?: string;
|
||||
shutterCount?: number;
|
||||
fee?: number;
|
||||
}
|
||||
|
||||
interface OrderDocumentModalProps {
|
||||
@@ -97,6 +105,8 @@ export function OrderDocumentModal({
|
||||
return "거래명세서";
|
||||
case "purchaseOrder":
|
||||
return "발주서";
|
||||
case "salesOrder":
|
||||
return "수주서";
|
||||
default:
|
||||
return "문서";
|
||||
}
|
||||
@@ -154,6 +164,30 @@ export function OrderDocumentModal({
|
||||
remarks={data.remarks}
|
||||
/>
|
||||
);
|
||||
case "salesOrder":
|
||||
return (
|
||||
<SalesOrderDocument
|
||||
documentNumber={data.documentNumber}
|
||||
orderNumber={data.lotNumber}
|
||||
certificationNumber={data.certificationNumber}
|
||||
orderDate={data.orderDate}
|
||||
client={data.client}
|
||||
siteName={data.siteName}
|
||||
manager={data.manager}
|
||||
managerContact={data.managerContact}
|
||||
deliveryRequestDate={data.deliveryRequestDate}
|
||||
expectedShipDate={data.expectedShipDate}
|
||||
deliveryMethod={data.deliveryMethod}
|
||||
address={data.address}
|
||||
recipientName={data.recipientName}
|
||||
recipientContact={data.recipientContact}
|
||||
shutterCount={data.shutterCount}
|
||||
fee={data.fee}
|
||||
items={data.items || []}
|
||||
products={data.products}
|
||||
remarks={data.remarks}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
647
src/components/orders/documents/SalesOrderDocument.tsx
Normal file
647
src/components/orders/documents/SalesOrderDocument.tsx
Normal file
@@ -0,0 +1,647 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 수주서 문서 컴포넌트
|
||||
* - 스크린샷 기반 디자인
|
||||
* - 제목 좌측, 결재란 우측
|
||||
* - 신청업체/신청내용/납품정보 3열 구조
|
||||
* - 스크린, 모터, 절곡물 테이블
|
||||
*/
|
||||
|
||||
import { getTodayString } from "@/utils/date";
|
||||
import { OrderItem } from "../actions";
|
||||
import { ProductInfo } from "./OrderDocumentModal";
|
||||
|
||||
interface SalesOrderDocumentProps {
|
||||
documentNumber?: string;
|
||||
orderNumber: string; // 로트번호
|
||||
certificationNumber?: string; // 인정번호
|
||||
orderDate?: string;
|
||||
client: string;
|
||||
siteName?: string;
|
||||
manager?: string;
|
||||
managerContact?: string;
|
||||
deliveryRequestDate?: string;
|
||||
expectedShipDate?: string;
|
||||
deliveryMethod?: string;
|
||||
address?: string;
|
||||
recipientName?: string;
|
||||
recipientContact?: string;
|
||||
shutterCount?: number;
|
||||
items?: OrderItem[];
|
||||
products?: ProductInfo[];
|
||||
remarks?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 수량 포맷 함수
|
||||
*/
|
||||
function formatQuantity(quantity: number, unit?: string): string {
|
||||
const countableUnits = ["EA", "SET", "PCS", "개", "세트", "BOX", "ROLL"];
|
||||
const upperUnit = (unit || "").toUpperCase();
|
||||
|
||||
if (countableUnits.includes(upperUnit)) {
|
||||
return Math.round(quantity).toLocaleString();
|
||||
}
|
||||
|
||||
const rounded = Math.round(quantity * 10000) / 10000;
|
||||
return rounded.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 4
|
||||
});
|
||||
}
|
||||
|
||||
export function SalesOrderDocument({
|
||||
documentNumber = "ABC123",
|
||||
orderNumber,
|
||||
certificationNumber = "-",
|
||||
orderDate = getTodayString(),
|
||||
client,
|
||||
siteName = "-",
|
||||
manager = "-",
|
||||
managerContact = "-",
|
||||
deliveryRequestDate = "-",
|
||||
expectedShipDate = "-",
|
||||
deliveryMethod = "상차",
|
||||
address = "-",
|
||||
recipientName = "-",
|
||||
recipientContact = "-",
|
||||
shutterCount = 0,
|
||||
items = [],
|
||||
products = [],
|
||||
remarks,
|
||||
}: SalesOrderDocumentProps) {
|
||||
// 스크린 제품만 필터링
|
||||
const screenProducts = products.filter(p =>
|
||||
p.productCategory?.includes("스크린") ||
|
||||
p.productName?.includes("스크린") ||
|
||||
p.productName?.includes("방화") ||
|
||||
p.productName?.includes("셔터")
|
||||
);
|
||||
|
||||
// 모터 아이템 필터링
|
||||
const motorItems = items.filter(item =>
|
||||
item.itemName?.toLowerCase().includes("모터") ||
|
||||
item.type?.includes("모터") ||
|
||||
item.itemCode?.startsWith("MT")
|
||||
);
|
||||
|
||||
// 브라켓 아이템 필터링
|
||||
const bracketItems = items.filter(item =>
|
||||
item.itemName?.includes("브라켓") ||
|
||||
item.type?.includes("브라켓")
|
||||
);
|
||||
|
||||
// 가이드레일 아이템 필터링
|
||||
const guideRailItems = items.filter(item =>
|
||||
item.itemName?.includes("가이드") ||
|
||||
item.itemName?.includes("레일") ||
|
||||
item.type?.includes("가이드")
|
||||
);
|
||||
|
||||
// 케이스 아이템 필터링
|
||||
const caseItems = items.filter(item =>
|
||||
item.itemName?.includes("케이스") ||
|
||||
item.itemName?.includes("셔터박스") ||
|
||||
item.type?.includes("케이스")
|
||||
);
|
||||
|
||||
// 하단마감재 아이템 필터링
|
||||
const bottomFinishItems = items.filter(item =>
|
||||
item.itemName?.includes("하단") ||
|
||||
item.itemName?.includes("마감") ||
|
||||
item.type?.includes("하단마감")
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-white p-8 min-h-full text-[11px]">
|
||||
{/* 헤더: 수주서 제목 (좌측) + 결재란 (우측) */}
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
{/* 수주서 제목 (좌측) */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-widest mb-2">수 주 서</h1>
|
||||
<div className="text-[10px] space-y-1">
|
||||
<div className="flex gap-4">
|
||||
<span>문서번호: <strong>{documentNumber}</strong></span>
|
||||
<span>작성일자: <strong>{orderDate}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 결재란 (우측) */}
|
||||
<table className="border border-gray-400 text-[10px]">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border-r border-gray-400 px-3 py-1">작성</th>
|
||||
<th className="border-r border-gray-400 px-3 py-1">승인</th>
|
||||
<th className="border-r border-gray-400 px-3 py-1">승인</th>
|
||||
<th className="px-3 py-1">승인</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-t border-gray-400">
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px] text-gray-500">과장</td>
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center"></td>
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center"></td>
|
||||
<td className="h-6 w-12 text-center"></td>
|
||||
</tr>
|
||||
<tr className="border-t border-gray-400">
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px]">홍길동</td>
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px]">이름</td>
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px]">이름</td>
|
||||
<td className="h-6 w-12 text-center text-[9px]">이름</td>
|
||||
</tr>
|
||||
<tr className="border-t border-gray-400">
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px] text-gray-500">부서명</td>
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px] text-gray-500">부서명</td>
|
||||
<td className="border-r border-gray-400 h-6 w-12 text-center text-[9px] text-gray-500">부서명</td>
|
||||
<td className="h-6 w-12 text-center text-[9px] text-gray-500">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 로트번호 / 인정번호 */}
|
||||
<div className="flex justify-end gap-8 mb-3 text-[10px]">
|
||||
<div className="flex items-center gap-2 border border-gray-400 px-3 py-1">
|
||||
<span className="bg-gray-100 px-2 py-0.5 font-medium">로트번호</span>
|
||||
<span>{orderNumber}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 border border-gray-400 px-3 py-1">
|
||||
<span className="bg-gray-100 px-2 py-0.5 font-medium">인정번호</span>
|
||||
<span>{certificationNumber}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 상품명/제품명 라인 */}
|
||||
<div className="flex gap-4 mb-3 text-[10px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">상품명</span>
|
||||
<span>{products[0]?.productCategory || "screen"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">제품명</span>
|
||||
<span>{products[0]?.productName || "-"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3열 섹션: 신청업체 | 신청내용 | 납품정보 */}
|
||||
<div className="border border-gray-400 mb-4">
|
||||
<div className="grid grid-cols-3">
|
||||
{/* 신청업체 */}
|
||||
<div className="border-r border-gray-400">
|
||||
<div className="bg-gray-200 text-center py-1 font-bold border-b border-gray-400">신청업체</div>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 w-20 font-medium">수주일</td>
|
||||
<td className="px-2 py-1">{orderDate}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">업체명</td>
|
||||
<td className="px-2 py-1">{client}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">수주 담당자</td>
|
||||
<td className="px-2 py-1">{manager}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">담당자 연락처</td>
|
||||
<td className="px-2 py-1">{managerContact}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">배송지 주소</td>
|
||||
<td className="px-2 py-1">{address}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 신청내용 */}
|
||||
<div className="border-r border-gray-400">
|
||||
<div className="bg-gray-200 text-center py-1 font-bold border-b border-gray-400">신청내용</div>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 w-20 font-medium">현장명</td>
|
||||
<td className="px-2 py-1">{siteName}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">납기요청일</td>
|
||||
<td className="px-2 py-1">{deliveryRequestDate}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">출고일</td>
|
||||
<td className="px-2 py-1">{expectedShipDate}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">셔터출수량</td>
|
||||
<td className="px-2 py-1">{shutterCount}개소</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium"> </td>
|
||||
<td className="px-2 py-1"> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 납품정보 */}
|
||||
<div>
|
||||
<div className="bg-gray-200 text-center py-1 font-bold border-b border-gray-400">납품정보</div>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 w-20 font-medium">현장명</td>
|
||||
<td className="px-2 py-1">{siteName}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">인수담당자</td>
|
||||
<td className="px-2 py-1">{recipientName}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">인수자연락처</td>
|
||||
<td className="px-2 py-1">{recipientContact}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium">배송방법</td>
|
||||
<td className="px-2 py-1">{deliveryMethod}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bg-gray-100 px-2 py-1 font-medium"> </td>
|
||||
<td className="px-2 py-1"> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[10px] mb-4">아래와 같이 주문하오니 품질 및 납기일을 준수하여 주시기 바랍니다.</p>
|
||||
|
||||
{/* 1. 스크린 테이블 */}
|
||||
<div className="mb-4">
|
||||
<p className="font-bold mb-2">1. 스크린</p>
|
||||
<div className="border border-gray-400">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-400 px-1 py-1 w-8" rowSpan={2}>No</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1 w-16" rowSpan={2}>품류</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1 w-14" rowSpan={2}>부호</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1" colSpan={2}>오픈사이즈</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1" colSpan={2}>제작사이즈</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1 w-24" rowSpan={2}>가이드레일<br/>유형</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1 w-14" rowSpan={2}>샤프트<br/>(인치)</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1 w-14" rowSpan={2}>케이스<br/>(인치)</th>
|
||||
<th className="border-r border-gray-400 px-1 py-1" colSpan={2}>모터</th>
|
||||
<th className="px-1 py-1 w-16" rowSpan={2}>마감</th>
|
||||
</tr>
|
||||
<tr className="bg-gray-50 border-b border-gray-400 text-[9px]">
|
||||
<th className="border-r border-gray-400 px-1">가로</th>
|
||||
<th className="border-r border-gray-400 px-1">세로</th>
|
||||
<th className="border-r border-gray-400 px-1">가로</th>
|
||||
<th className="border-r border-gray-400 px-1">세로</th>
|
||||
<th className="border-r border-gray-400 px-1">브라켓트</th>
|
||||
<th className="border-r border-gray-400 px-1">용량Kg</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{screenProducts.length > 0 ? (
|
||||
screenProducts.map((product, index) => (
|
||||
<tr key={index} className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{index + 1}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{product.productCategory || "-"}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{product.code || "-"}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{product.openWidth || "-"}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{product.openHeight || "-"}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{product.openWidth || "-"}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">{product.openHeight || "-"}</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center text-[9px]">백면형<br/>(120X70)</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">5</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">5</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">380X180</td>
|
||||
<td className="border-r border-gray-300 px-1 py-1 text-center">300</td>
|
||||
<td className="px-1 py-1 text-center">SUS마감</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={13} className="px-2 py-3 text-center text-gray-400">
|
||||
등록된 스크린 제품이 없습니다
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 2. 모터 테이블 */}
|
||||
<div className="mb-4">
|
||||
<p className="font-bold mb-2">2. 모터</p>
|
||||
<div className="border border-gray-400">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-28">항목</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">구분</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-28">규격</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-14">수량</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-28">항목</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">구분</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-28">규격</th>
|
||||
<th className="px-2 py-1 w-14">수량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(motorItems.length > 0 || bracketItems.length > 0) ? (
|
||||
<>
|
||||
{/* 모터 행 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1">모터(380V 단상)</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">모터 용량</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">{motorItems[0]?.spec || "KD-150K"}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{motorItems[0] ? formatQuantity(motorItems[0].quantity, motorItems[0].unit) : "6"}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">모터(380V 단상)</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">모터 용량</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">{motorItems[1]?.spec || "KD-150K"}</td>
|
||||
<td className="px-2 py-1 text-center">{motorItems[1] ? formatQuantity(motorItems[1].quantity, motorItems[1].unit) : "6"}</td>
|
||||
</tr>
|
||||
{/* 브라켓트 행 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1">브라켓트</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">브라켓트</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">{bracketItems[0]?.spec || "380X180 [2-4\"]"}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{bracketItems[0] ? formatQuantity(bracketItems[0].quantity, bracketItems[0].unit) : "6"}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">브라켓트</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">브라켓트</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">{bracketItems[1]?.spec || "380X180 [2-4\"]"}</td>
|
||||
<td className="px-2 py-1 text-center">{bracketItems[1] ? formatQuantity(bracketItems[1].quantity, bracketItems[1].unit) : "6"}</td>
|
||||
</tr>
|
||||
{/* 브라켓트 추가 행 (밑침통 영금) */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1">브라켓트</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">밑침통 영금</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">{bracketItems[2]?.spec || "∠40-40 L380"}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{bracketItems[2] ? formatQuantity(bracketItems[2].quantity, bracketItems[2].unit) : "44"}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1"></td>
|
||||
<td className="border-r border-gray-300 px-2 py-1"></td>
|
||||
<td className="border-r border-gray-300 px-2 py-1"></td>
|
||||
<td className="px-2 py-1 text-center"></td>
|
||||
</tr>
|
||||
</>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-2 py-3 text-center text-gray-400">
|
||||
등록된 모터/브라켓 품목이 없습니다
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3. 절곡물 */}
|
||||
<div className="mb-4">
|
||||
<p className="font-bold mb-2">3. 절곡물</p>
|
||||
|
||||
{/* 3-1. 가이드레일 */}
|
||||
<div className="mb-3">
|
||||
<p className="text-[10px] font-medium mb-1">3-1. 가이드레일 - EGI 1.5ST + 마감재 EGI 1.1ST + 별도마감재 SUS 1.1ST</p>
|
||||
<div className="border border-gray-400">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-24">백면형 (120X70)</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-16">길이</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-12">수량</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-24">측면형 (120X120)</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-16">길이</th>
|
||||
<th className="px-2 py-1 w-12">수량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{guideRailItems.length > 0 ? (
|
||||
<>
|
||||
{/* 1행: L: 3,000 / 22 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-400" rowSpan={4}>
|
||||
<div className="flex items-center justify-center h-20 border border-dashed border-gray-300">
|
||||
IMG
|
||||
</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">L: 3,000</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">22</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-400" rowSpan={4}>
|
||||
<div className="flex items-center justify-center h-20 border border-dashed border-gray-300">
|
||||
IMG
|
||||
</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">L: 3,000</td>
|
||||
<td className="px-2 py-1 text-center">22</td>
|
||||
</tr>
|
||||
{/* 2행: 하부BASE */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-[9px]">하부BASE<br/>[130X80]</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">22</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center"></td>
|
||||
<td className="px-2 py-1 text-center"></td>
|
||||
</tr>
|
||||
{/* 3행: 빈 행 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center"></td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center"></td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center"></td>
|
||||
<td className="px-2 py-1 text-center"></td>
|
||||
</tr>
|
||||
{/* 4행: 제품명 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center bg-gray-100">제품명</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">KSS01</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center bg-gray-100">제품명</td>
|
||||
<td className="px-2 py-1 text-center">KSS01</td>
|
||||
</tr>
|
||||
</>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-2 py-2 text-center text-gray-400">
|
||||
등록된 가이드레일이 없습니다
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 연기차단재 정보 */}
|
||||
<div className="mt-2 border border-gray-400">
|
||||
<table className="w-full text-[10px]">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border-r border-gray-300 px-2 py-1 w-32" rowSpan={2}>
|
||||
<div className="font-medium">연기차단재(W50)</div>
|
||||
<div>• 가이드레일 마감재</div>
|
||||
<div className="text-red-600 font-medium">양측에 설치</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 w-32" rowSpan={2}>
|
||||
<div>EGI 0.8T +</div>
|
||||
<div>화이버글라스코팅직물</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-400 w-20" rowSpan={2}>
|
||||
<div className="flex items-center justify-center h-10 border border-dashed border-gray-300">
|
||||
IMG
|
||||
</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 bg-gray-100">규격</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">3,000</td>
|
||||
<td className="px-2 py-1 text-center">4,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-r border-gray-300 px-2 py-1 bg-gray-100">수량</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">44</td>
|
||||
<td className="px-2 py-1 text-center">1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 text-[10px]">
|
||||
<span className="font-medium">• 별도 추가사항</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3-2. 케이스(셔터박스) */}
|
||||
<div className="mb-3">
|
||||
<p className="text-[10px] font-medium mb-1">3-2. 케이스(셔터박스) - EGI 1.5ST</p>
|
||||
<div className="border border-gray-400">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-24"> </th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-24">규격</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">길이</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-12">수량</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">측면부</th>
|
||||
<th className="px-2 py-1 w-12">수량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{caseItems.length > 0 ? (
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-400" rowSpan={3}>
|
||||
<div className="flex items-center justify-center h-16 border border-dashed border-gray-300">
|
||||
IMG
|
||||
</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-[9px]">
|
||||
500X330<br/>(150X300,<br/>400K원)
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-[9px]">
|
||||
L: 4,000<br/>L: 5,000<br/>상부덮개<br/>(1219X389)
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-[9px]">
|
||||
3<br/>4<br/>55
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">500X355</td>
|
||||
<td className="px-2 py-1 text-center">22</td>
|
||||
</tr>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-2 py-2 text-center text-gray-400">
|
||||
등록된 케이스가 없습니다
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 연기차단재 정보 */}
|
||||
<div className="mt-2 border border-gray-400">
|
||||
<table className="w-full text-[10px]">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border-r border-gray-300 px-2 py-1 w-32" rowSpan={2}>
|
||||
<div className="font-medium">연기차단재(W50)</div>
|
||||
<div>• 판넬부, 전면부</div>
|
||||
<div className="text-red-600 font-medium">감싸에 설치</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 w-32" rowSpan={2}>
|
||||
<div>EGI 0.8T +</div>
|
||||
<div>화이버글라스코팅직물</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-400 w-20" rowSpan={2}>
|
||||
<div className="flex items-center justify-center h-10 border border-dashed border-gray-300">
|
||||
IMG
|
||||
</div>
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 bg-gray-100">규격</td>
|
||||
<td className="px-2 py-1 text-center">3,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-r border-gray-300 px-2 py-1 bg-gray-100">수량</td>
|
||||
<td className="px-2 py-1 text-center">44</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3-3. 하단마감재 */}
|
||||
<div className="mb-3">
|
||||
<p className="text-[10px] font-medium mb-1">3-3. 하단마감재 - 하단마감재(EGI 1.5ST) + 하단보강앨비(EGI 1.5ST) + 하단 보강철(EGI 1.1ST) + 하단 무게형 철(50X12T)</p>
|
||||
<div className="border border-gray-400">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">구성품</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-16">길이</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-12">수량</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">구성품</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-16">길이</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-12">수량</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-20">구성품</th>
|
||||
<th className="border-r border-gray-400 px-2 py-1 w-16">길이</th>
|
||||
<th className="px-2 py-1 w-12">수량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{bottomFinishItems.length > 0 ? (
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-[9px]">하단마감재<br/>(60X40)</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">L: 4,000</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">11</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-[9px]">하단보강<br/>(60X17)</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">L: 4,000</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">11</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-[9px]">하단무게<br/>[50X12T]</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">L: 4,000</td>
|
||||
<td className="px-2 py-1 text-center">11</td>
|
||||
</tr>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={9} className="px-2 py-2 text-center text-gray-400">
|
||||
등록된 하단마감재가 없습니다
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 특이사항 */}
|
||||
{remarks && (
|
||||
<div className="mb-4">
|
||||
<p className="font-bold mb-2">【 특이사항 】</p>
|
||||
<div className="border border-gray-400 p-3 min-h-[40px]">
|
||||
{remarks}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
export { ContractDocument } from "./ContractDocument";
|
||||
export { TransactionDocument } from "./TransactionDocument";
|
||||
export { PurchaseOrderDocument } from "./PurchaseOrderDocument";
|
||||
export { SalesOrderDocument } from "./SalesOrderDocument";
|
||||
export {
|
||||
OrderDocumentModal,
|
||||
type OrderDocumentType,
|
||||
|
||||
@@ -12,6 +12,7 @@ export {
|
||||
deleteOrders,
|
||||
updateOrderStatus,
|
||||
getOrderStats,
|
||||
createProductionOrder,
|
||||
revertProductionOrder,
|
||||
revertOrderConfirmation,
|
||||
getQuoteByIdForSelect,
|
||||
|
||||
Reference in New Issue
Block a user