feat: 수주등록 모달 및 범용 컴포넌트 개발
- 범용 컴포넌트 3종 개발 및 레지스트리 등록: * AutocompleteSearchInput: 자동완성 검색 입력 컴포넌트 * EntitySearchInput: 엔티티 검색 모달 컴포넌트 * ModalRepeaterTable: 모달 기반 반복 테이블 컴포넌트 - 수주등록 전용 컴포넌트: * OrderCustomerSearch: 거래처 검색 (AutocompleteSearchInput 래퍼) * OrderItemRepeaterTable: 품목 관리 (ModalRepeaterTable 래퍼) * OrderRegistrationModal: 수주등록 메인 모달 - 백엔드 API: * Entity 검색 API (멀티테넌시 지원) * 수주 등록 API (자동 채번) - 화면 편집기 통합: * 컴포넌트 레지스트리에 등록 * ConfigPanel을 통한 설정 기능 * 드래그앤드롭으로 배치 가능 - 개발 문서: * 수주등록_화면_개발_계획서.md (상세 설계 문서)
This commit is contained in:
138
frontend/app/test-entity-search/page.tsx
Normal file
138
frontend/app/test-entity-search/page.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { EntitySearchInputComponent } from "@/lib/registry/components/entity-search-input";
|
||||
import { AutocompleteSearchInputComponent } from "@/lib/registry/components/autocomplete-search-input";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export default function TestEntitySearchPage() {
|
||||
const [customerCode, setCustomerCode] = useState<string>("");
|
||||
const [customerData, setCustomerData] = useState<any>(null);
|
||||
|
||||
const [itemCode, setItemCode] = useState<string>("");
|
||||
const [itemData, setItemData] = useState<any>(null);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">EntitySearchInput 테스트</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
엔티티 검색 입력 컴포넌트 동작 테스트
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 거래처 검색 테스트 - 자동완성 방식 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>거래처 검색 (자동완성 드롭다운 방식) ⭐ NEW</CardTitle>
|
||||
<CardDescription>
|
||||
타이핑하면 바로 드롭다운이 나타나는 방식 - 수주 등록에서 사용
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>거래처</Label>
|
||||
<AutocompleteSearchInputComponent
|
||||
tableName="customer_mng"
|
||||
displayField="customer_name"
|
||||
valueField="customer_code"
|
||||
searchFields={["customer_name", "customer_code", "business_number"]}
|
||||
placeholder="거래처명 입력하여 검색"
|
||||
showAdditionalInfo
|
||||
additionalFields={["customer_code", "address", "contact_phone"]}
|
||||
value={customerCode}
|
||||
onChange={(code, fullData) => {
|
||||
setCustomerCode(code || "");
|
||||
setCustomerData(fullData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{customerData && (
|
||||
<div className="mt-4 p-4 bg-muted rounded-md">
|
||||
<h3 className="font-semibold mb-2">선택된 거래처 정보:</h3>
|
||||
<pre className="text-xs">
|
||||
{JSON.stringify(customerData, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 거래처 검색 테스트 - 모달 방식 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>거래처 검색 (모달 방식)</CardTitle>
|
||||
<CardDescription>
|
||||
버튼 클릭 → 모달 열기 → 검색 및 선택 방식
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>거래처</Label>
|
||||
<EntitySearchInputComponent
|
||||
tableName="customer_mng"
|
||||
displayField="customer_name"
|
||||
valueField="customer_code"
|
||||
searchFields={["customer_name", "customer_code", "business_number"]}
|
||||
mode="combo"
|
||||
placeholder="거래처를 검색하세요"
|
||||
modalTitle="거래처 검색 및 선택"
|
||||
modalColumns={["customer_code", "customer_name", "address", "contact_phone"]}
|
||||
showAdditionalInfo
|
||||
additionalFields={["address", "contact_phone", "business_number"]}
|
||||
value={customerCode}
|
||||
onChange={(code, fullData) => {
|
||||
setCustomerCode(code || "");
|
||||
setCustomerData(fullData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 품목 검색 테스트 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>품목 검색 (Modal 모드)</CardTitle>
|
||||
<CardDescription>
|
||||
item_info 테이블에서 품목을 검색합니다
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>품목</Label>
|
||||
<EntitySearchInputComponent
|
||||
tableName="item_info"
|
||||
displayField="item_name"
|
||||
valueField="id"
|
||||
searchFields={["item_name", "id", "item_number"]}
|
||||
mode="modal"
|
||||
placeholder="품목 선택"
|
||||
modalTitle="품목 검색"
|
||||
modalColumns={["id", "item_name", "item_number", "unit", "selling_price"]}
|
||||
showAdditionalInfo
|
||||
additionalFields={["item_number", "unit", "selling_price"]}
|
||||
value={itemCode}
|
||||
onChange={(code, fullData) => {
|
||||
setItemCode(code || "");
|
||||
setItemData(fullData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{itemData && (
|
||||
<div className="mt-4 p-4 bg-muted rounded-md">
|
||||
<h3 className="font-semibold mb-2">선택된 품목 정보:</h3>
|
||||
<pre className="text-xs">
|
||||
{JSON.stringify(itemData, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
87
frontend/app/test-order-registration/page.tsx
Normal file
87
frontend/app/test-order-registration/page.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { OrderRegistrationModal } from "@/components/order/OrderRegistrationModal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
export default function TestOrderRegistrationPage() {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const handleSuccess = () => {
|
||||
console.log("수주 등록 성공!");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">수주 등록 테스트</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
EntitySearchInput + ModalRepeaterTable을 활용한 수주 등록 화면
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>수주 등록 모달</CardTitle>
|
||||
<CardDescription>
|
||||
모달 버튼을 클릭하여 수주 등록 화면을 테스트하세요
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button onClick={() => setModalOpen(true)}>
|
||||
수주 등록 모달 열기
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>구현된 기능</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>EntitySearchInput: 거래처 검색 및 선택 (콤보 모드)</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>ModalRepeaterTable: 품목 검색 및 동적 추가</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>자동 계산: 수량 × 단가 = 금액</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>인라인 편집: 수량, 단가, 납품일, 비고 수정 가능</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>중복 방지: 이미 추가된 품목은 선택 불가</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>행 삭제: 추가된 품목 개별 삭제 가능</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>전체 금액 표시: 모든 품목 금액의 합계</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span>입력 방식 전환: 거래처 우선 / 견대 방식 / 단가 방식</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 수주 등록 모달 */}
|
||||
<OrderRegistrationModal
|
||||
open={modalOpen}
|
||||
onOpenChange={setModalOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user