Files
wace_rps/frontend/components/common/PartSelect.tsx
T
hjjeong 489fa50d11 영업관리 4개 메뉴 검색폼 wace 일치 + 공통 UX(초기화·date input) 정비
- 검색 폼 정합성: 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>
2026-05-08 10:42:16 +09:00

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}
/>
);
}