489fa50d11
- 검색 폼 정합성: wace JSP `<!-- 주석처리된 검색필터 -->` 블록까지 잘못 이식했던 부분 정정 - 견적: 11→7개 (제품구분/국내해외/유무상/요청납기 제거) - 주문: 13→9개 (제품구분/국내해외/유무상/견적환종 제거) - 매출: 10→11개 (출하지시상태 제거 + 제품구분·국내/해외 추가, JSP 순서로 재배치) - 판매: 변경 없음 (원본 그대로 일치) - 매출 백엔드: SaleListFilter에 productType/nation 추가, getRevenueList에 partObjId/serialNo/orderDate/productType/nation 5개 필터 처리 - 공통 UX - 초기화 버튼을 4개 메뉴 동일하게 통일 (variant=ghost, 버튼 영역 끝) - <Input type="date">는 빈 값 placeholder 숨김 + 캘린더 아이콘 숨김 + 영역 클릭으로 picker 자동(showPicker) - 신규 공통 컴포넌트: CommCodeSelect/CustomerSelect/CustomerSearchDialog/PartSelect/ItemSearchDialog + backend salesCommonRoutes - 문서: 01/02/04 검색 폼 표를 활성/비활성 분리 형식으로 정정, README에 8. 공통 UX 규칙 섹션 신설 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
2.5 KiB
TypeScript
95 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
|
|
import { apiClient } from "@/lib/api/client";
|
|
|
|
/**
|
|
* 품번/품명 자동완성 셀렉트
|
|
*
|
|
* wace_plm orderMgmtList의 select2-part AJAX 패턴을 단순화한 형태.
|
|
* - item_info 전체를 한 번 캐시 (id 기준 단일 소스)
|
|
* - mode='partNo': 라벨로 item_number 표시
|
|
* - mode='partName': 라벨로 item_name 표시
|
|
* - 선택값(value)은 양쪽 모두 item_info.id (= part_objid)
|
|
*/
|
|
|
|
interface PartRow {
|
|
id: string;
|
|
item_number?: string;
|
|
item_name?: string;
|
|
}
|
|
|
|
interface PartSelectProps {
|
|
mode: "partNo" | "partName";
|
|
/** item_info.id (part_objid) */
|
|
value: string;
|
|
onValueChange: (partObjId: string) => void;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
let cachedRows: PartRow[] | null = null;
|
|
let inflight: Promise<PartRow[]> | null = null;
|
|
|
|
const fetchParts = async (): Promise<PartRow[]> => {
|
|
if (cachedRows) return cachedRows;
|
|
if (inflight) return inflight;
|
|
inflight = (async () => {
|
|
// 영업관리 4개 메뉴 공통 endpoint — wace 이식 8179건 + COMPANY_16 데이터.
|
|
const res = await apiClient.get("/sales/parts");
|
|
const rows = (res.data?.data ?? []) as any[];
|
|
cachedRows = rows
|
|
.filter((r) => r.id != null)
|
|
.map((r) => ({
|
|
id: String(r.id),
|
|
item_number: r.item_number ?? "",
|
|
item_name: r.item_name ?? "",
|
|
}));
|
|
return cachedRows!;
|
|
})();
|
|
try {
|
|
return await inflight;
|
|
} finally {
|
|
inflight = null;
|
|
}
|
|
};
|
|
|
|
const toOptions = (rows: PartRow[], mode: PartSelectProps["mode"]): SmartSelectOption[] =>
|
|
rows
|
|
.filter((r) => mode === "partNo" ? r.item_number : r.item_name)
|
|
.map((r) => ({
|
|
code: r.id,
|
|
label: String(mode === "partNo" ? r.item_number : r.item_name),
|
|
}));
|
|
|
|
export function PartSelect({
|
|
mode, value, onValueChange,
|
|
placeholder = mode === "partNo" ? "품번 선택" : "품명 선택",
|
|
disabled, className,
|
|
}: PartSelectProps) {
|
|
const [options, setOptions] = useState<SmartSelectOption[]>(
|
|
cachedRows ? toOptions(cachedRows, mode) : [],
|
|
);
|
|
|
|
useEffect(() => {
|
|
let alive = true;
|
|
fetchParts()
|
|
.then((rows) => { if (alive) setOptions(toOptions(rows, mode)); })
|
|
.catch(() => {});
|
|
return () => { alive = false; };
|
|
}, [mode]);
|
|
|
|
return (
|
|
<SmartSelect
|
|
options={options}
|
|
value={value}
|
|
onValueChange={onValueChange}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
className={className}
|
|
/>
|
|
);
|
|
}
|