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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user