Merge remote-tracking branch 'origin/master'

# Conflicts:
#	src/components/quotes/QuoteRegistration.tsx
This commit is contained in:
2026-01-20 20:49:14 +09:00
17 changed files with 1146 additions and 2286 deletions

View File

@@ -3,6 +3,7 @@
/**
* 수주 등록 컴포넌트
*
* IntegratedDetailTemplate 마이그레이션 (2026-01-20)
* - 견적 불러오기 섹션
* - 기본 정보 섹션
* - 수주/배송 정보 섹션
@@ -19,7 +20,6 @@ import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import {
@@ -44,12 +44,15 @@ import {
Plus,
Trash2,
Info,
MapPin,
Truck,
Package,
MessageSquare,
} from "lucide-react";
import { toast } from "sonner";
import {
ResponsiveFormTemplate,
FormSection,
} from "@/components/templates/ResponsiveFormTemplate";
import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate";
import { orderCreateConfig, orderEditConfig } from "./orderConfig";
import { FormSection } from "@/components/organisms/FormSection";
import { QuotationSelectDialog } from "./QuotationSelectDialog";
import { type QuotationForSelect, type QuotationItem } from "./actions";
import { ItemAddDialog, OrderItem } from "./ItemAddDialog";
@@ -179,6 +182,9 @@ export function OrderRegistration({
const [isSaving, setIsSaving] = useState(false);
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
// Config 선택
const config = isEditMode ? orderEditConfig : orderCreateConfig;
// 거래처 목록 조회
const { clients, fetchClients, isLoading: isClientsLoading } = useClientList();
@@ -349,7 +355,7 @@ export function OrderRegistration({
}, []);
// 저장 핸들러
const handleSave = async () => {
const handleSave = useCallback(async () => {
// 유효성 검사
const errors = validateForm();
setFieldErrors(errors);
@@ -366,21 +372,12 @@ export function OrderRegistration({
} finally {
setIsSaving(false);
}
};
}, [form, validateForm, onSave]);
return (
<>
<ResponsiveFormTemplate
title={isEditMode ? "수주 수정" : "수주 등록"}
description="견적을 수주로 전환하거나 새 수주를 등록합니다"
icon={FileText}
onSave={handleSave}
onCancel={onBack}
saveLabel="저장"
cancelLabel="취소"
saveLoading={isSaving}
saveDisabled={isSaving}
>
// 폼 콘텐츠 렌더링
const renderFormContent = useCallback(
() => (
<div className="space-y-6 max-w-4xl">
{/* Validation 에러 Alert */}
{Object.keys(fieldErrors).length > 0 && (
<Alert className="bg-red-50 border-red-200">
@@ -411,13 +408,12 @@ export function OrderRegistration({
)}
{/* 견적 불러오기 섹션 */}
<FormSection title="견적 불러오기" icon={Search}>
<FormSection
title="견적 불러오기"
description="확정된 견적을 선택하면 정보가 자동으로 채워집니다"
icon={Search}
>
<div className="space-y-4">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Info className="h-4 w-4" />
</div>
{form.selectedQuotation ? (
<div className="p-4 border rounded-lg bg-muted/30">
<div className="flex items-start justify-between">
@@ -463,7 +459,11 @@ export function OrderRegistration({
</FormSection>
{/* 기본 정보 섹션 */}
<FormSection title="기본 정보" icon={FileText}>
<FormSection
title="기본 정보"
description="발주처 및 현장 정보를 입력하세요"
icon={FileText}
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>
@@ -544,7 +544,11 @@ export function OrderRegistration({
</FormSection>
{/* 수주/배송 정보 섹션 */}
<FormSection title="수주/배송 정보">
<FormSection
title="수주/배송 정보"
description="출고 및 배송 정보를 입력하세요"
icon={Truck}
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* 출고예정일 */}
<div className="space-y-2">
@@ -719,7 +723,11 @@ export function OrderRegistration({
</FormSection>
{/* 수신처 주소 섹션 */}
<FormSection title="수신처 주소">
<FormSection
title="수신처 주소"
description="배송지 주소를 입력하세요"
icon={MapPin}
>
<div className="space-y-4">
<div className="flex gap-2">
<Input
@@ -752,7 +760,11 @@ export function OrderRegistration({
</FormSection>
{/* 비고 섹션 */}
<FormSection title="비고">
<FormSection
title="비고"
description="특이사항을 입력하세요"
icon={MessageSquare}
>
<Textarea
placeholder="특이사항을 입력하세요"
value={form.remarks}
@@ -764,7 +776,11 @@ export function OrderRegistration({
</FormSection>
{/* 품목 내역 섹션 */}
<FormSection title="품목 내역">
<FormSection
title="품목 내역"
description="수주 품목을 관리하세요"
icon={Package}
>
<div className="space-y-4">
{/* 품목 에러 메시지 */}
{fieldErrors.items && (
@@ -883,7 +899,33 @@ export function OrderRegistration({
</div>
</div>
</FormSection>
</ResponsiveFormTemplate>
</div>
),
[
form,
fieldErrors,
clients,
isClientsLoading,
openPostcode,
clearFieldError,
handleClearQuotation,
handleQuantityChange,
handleRemoveItem,
]
);
return (
<>
<IntegratedDetailTemplate
config={config}
mode={isEditMode ? "edit" : "create"}
isLoading={false}
isSubmitting={isSaving}
onBack={onBack}
onCancel={onBack}
onSubmit={handleSave}
renderForm={renderFormContent}
/>
{/* 견적 선택 팝업 */}
<QuotationSelectDialog
@@ -901,4 +943,4 @@ export function OrderRegistration({
/>
</>
);
}
}

View File

@@ -0,0 +1,38 @@
'use client';
import { FileText } from 'lucide-react';
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
/**
* 수주 등록 페이지 Config
*/
export const orderCreateConfig: DetailConfig = {
title: '수주 등록',
description: '견적을 수주로 전환하거나 새 수주를 등록합니다',
icon: FileText,
basePath: '/sales/order-management',
fields: [],
actions: {
showBack: true,
showSave: true,
submitLabel: '저장',
backLabel: '취소',
},
};
/**
* 수주 수정 페이지 Config
*/
export const orderEditConfig: DetailConfig = {
title: '수주 수정',
description: '수주 정보를 수정합니다',
icon: FileText,
basePath: '/sales/order-management',
fields: [],
actions: {
showBack: true,
showSave: true,
submitLabel: '저장',
backLabel: '취소',
},
};