Files
wace_rps/frontend/components/development/DevPartSelect.tsx
T
hjjeong 20a429eecb 개발관리>E-BOM 화면 운영판 1:1 정정 다수 — 검색폼·상태변경·체크박스·STATUS 표시
사용자 검증으로 발견된 5가지 함정 일괄 정정.

(1) ebom-search 검색폼 운영판 1:1 — wace structureAscendingList.jsp 노출 필드만:
   - 제거: 프로젝트 OBJID (raw input), UNIT_CODE (raw input)
     운영판도 고객사/프로젝트번호/유닛명 모두 주석 처리되어 노출 안 됨
   - 유지: 품번 / 품명 / 표시 레벨 (1~5 select)
   - BomTreeFilter.search_level 추가 + ascending/descending CTE 에 T.lev <= $search_level::int

(2) 품번/품명 자동완성 (wace select2-part 1:1):
   - 영업관리 PartSelect 는 item_info 마스터 기반 → 개발관리(part_mng)용 별도 컴포넌트 신설
   - backend GET /api/development/part/options : IS_LAST='1' part_mng 전체 (영업관리 sales/parts 패턴)
   - frontend DevPartSelect.tsx : SmartSelect 캐시 + mode partNo/partName 분리
   - ebom-search 페이지 단순 Input → DevPartSelect 교체
   - 품번 선택 시 품명 자동 채움 / 품명 선택 시 품번 자동 채움 (운영판 select2-part 1:1)

(3) BomReportStatusDialog 운영판 1:1 재작성 — wace structureStatusChangePopup.jsp:
   - 잘못된 점: read-only 박스 + 상태 select(create/changeDesign/deploy 3옵션)
   - 정정: 5필드 모두 편집 가능 (CommCodeSelect 제품구분 / 품번 input / 품명 input /
           Version input / 상태 Y/N 라디오) — 운영 매퍼 updateStructureStatus 5컬럼 UPDATE 1:1
   - 헤더 파란 바 + 4컬럼 테이블(25%/75%) + 저장/닫기 중앙 배치 (운영판 스타일 1:1)

(4) DataGrid id 매핑 — 체크박스 ID 키 불일치 함정:
   - DataGrid 는 row.id 로 체크박스 ID 관리, 백엔드 응답은 row.objid (postgres lowercase)
   - 결과: checkedIds[0] 가 undefined → 상태변경/수정/삭제 다이얼로그가 objid=undefined 로 열려
     detail 호출 안 됨 → 빈 폼 표시 (사용자 지적 "기본 정보 표시 안됨")
   - 일괄 수정 (3 페이지) : ebom-regist / part-regist / part-search
     gridRows = useMemo(() => rows.map(r => ({ ...r, id: r.objid })), [rows])
     영업관리 페이지 동일 패턴 1:1

(5) STATUS_TITLE 매핑 운영판 1:1 — 운영 그리드는 'Y'/'N' 글자 그대로 표시:
   CASE UPPER(T.STATUS)
     WHEN 'CREATE' THEN '등록중'
     WHEN 'CHANGEDESIGN' THEN '설계변경미배포'
     WHEN 'DEPLOY' THEN '배포완료'
     ELSE COALESCE(T.STATUS, '') END AS STATUS_TITLE
   - 운영 매퍼는 ELSE '' 이지만 RPS 는 raw fallback (사용자 화면에서 식별 가능)
   - 'Y'/'N' 매핑 라벨 추가 → 운영 스크린샷 확인 후 제거 (운영판은 raw)

미해결 (별 작업):
- 확정일 (DEPLOY_DATE) 표시 — 운영판은 별도 "배포" 액션 (deployBomReport 매퍼) 으로 채움.
  RPS ebom-regist 에 배포 버튼 미구현 → 신규 BOM 확정일 빈값. 별 PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:43:29 +09:00

103 lines
3.1 KiB
TypeScript

"use client";
// 개발관리 PART 자동완성 셀렉트 — wace select2-part 1:1 (영업관리 PartSelect 패턴 동일).
//
// 영업관리 PartSelect 는 item_info(영업 마스터) 기반.
// 개발관리는 part_mng 기반이므로 별도 컴포넌트.
//
// - part_mng IS_LAST='1' 전체를 한 번 캐시 (objid 기준 단일 소스)
// - mode='partNo' : 라벨로 part_no 표시
// - mode='partName': 라벨로 part_name 표시
// - 선택 시 onValueChange(part_no/part_name 텍스트, 원본 row) — ebom-search 가 LIKE 가 아닌
// 완전일치 검색을 하므로 value 는 텍스트 그대로 전달
import React, { useEffect, useState } from "react";
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
import { apiClient } from "@/lib/api/client";
interface DevPartRow {
objid: string;
part_no: string;
part_name: string;
}
interface DevPartSelectProps {
mode: "partNo" | "partName";
/** 현재 선택값 — part_no 또는 part_name 텍스트 */
value: string;
/** 선택 시 텍스트 + 원본 row */
onValueChange: (value: string, row?: DevPartRow) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
}
let cachedRows: DevPartRow[] | null = null;
let inflight: Promise<DevPartRow[]> | null = null;
const fetchParts = async (): Promise<DevPartRow[]> => {
if (cachedRows) return cachedRows;
if (inflight) return inflight;
inflight = (async () => {
const res = await apiClient.get("/development/part/options");
const rows = (res.data?.data?.rows ?? []) as any[];
cachedRows = rows
.filter((r) => r.objid != null)
.map((r) => ({
objid: String(r.objid),
part_no: r.part_no ?? "",
part_name: r.part_name ?? "",
}));
return cachedRows!;
})();
try {
return await inflight;
} finally {
inflight = null;
}
};
const toOptions = (rows: DevPartRow[], mode: DevPartSelectProps["mode"]): SmartSelectOption[] => {
const seen = new Set<string>();
const result: SmartSelectOption[] = [];
for (const r of rows) {
const label = mode === "partNo" ? r.part_no : r.part_name;
if (!label || seen.has(label)) continue;
seen.add(label);
result.push({ code: label, label });
}
return result;
};
export function DevPartSelect({
mode, value, onValueChange,
placeholder = mode === "partNo" ? "품번 입력하여 검색..." : "품명 입력하여 검색...",
disabled, className,
}: DevPartSelectProps) {
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={(v) => {
const row = cachedRows?.find((r) => (mode === "partNo" ? r.part_no : r.part_name) === v);
onValueChange(v, row);
}}
placeholder={placeholder}
disabled={disabled}
className={className}
/>
);
}