프로젝트 상세 정보 다이얼로그 wace 운영판 1:1 + 영업관리 판매·매출관리 연결

· 다이얼로그 디자인: 프로젝트정보 박스(주문유형/제품구분/국내해외/고객사·유무상/접수일/견적환종/견적환율) + 품목정보 테이블(No/품번/품명/S/N/요청납기/고객요청사항/반납사유)
· ProjectInfoData 정규화 props — 진행관리/판매관리/매출관리 각각 toProjectInfo 매핑으로 호출
· backend SQL 3곳 보강: exchange_rate (contract_mgmt) + customer_request (CI→CM fallback) + return_reason_name (CI CODE_NAME)
· 판매관리/매출관리 page에 columns useMemo + project_no 셀 onClick + ProjectInfoDialog state 추가
· wace 운영 URL: /salesMgmt/salesRegForm.do?saleNo=detail 1:1 매핑 (새 창 → 같은 탭 다이얼로그)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-12 13:44:58 +09:00
parent 50669a66ee
commit 5c085dc69e
8 changed files with 215 additions and 48 deletions
@@ -21,9 +21,27 @@ import { CommCodeSelect } from "@/components/common/CommCodeSelect";
import { CustomerSelect } from "@/components/common/CustomerSelect";
import { PartSelect } from "@/components/common/PartSelect";
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
import { ProjectInfoDialog } from "@/components/project/ProjectInfoDialog";
import { ProjectInfoDialog, ProjectInfoData } from "@/components/project/ProjectInfoDialog";
import { projectMgmtApi, ProgressListFilter, ProgressRow } from "@/lib/api/projectMgmt";
// 진행관리 row → 정규화된 ProjectInfoData 매핑
const toProjectInfo = (r: ProgressRow): ProjectInfoData => ({
orderType: r.category_name,
productType: r.product_name,
area: r.area_name,
customer: r.customer_name,
paidType: r.free_of_charge,
regDate: r.reg_date,
currency: r.contract_currency_name,
exchangeRate: r.exchange_rate,
partNo: r.product_item_code,
partName: r.product_item_name,
serialNo: r.serial_no,
reqDelDate: r.req_del_date,
customerRequest: r.customer_request,
returnReason: r.return_reason_name,
});
// wace projectMgmtWbsList3.jsp 컬럼 정의 1:1 (8그룹 → 평탄화, 그룹명은 라벨 prefix)
const GRID_COLUMNS: DataGridColumn[] = [
{ key: "project_no", label: "프로젝트번호", width: "w-[160px]", frozen: true },
@@ -82,13 +100,13 @@ export default function ProjectProgressPage() {
// wace `fn_openSaleRegPopup(PROJECT_NO, "detail")` 대응 — PROJECT_NO 셀 클릭 시 프로젝트 정보 다이얼로그
const [infoOpen, setInfoOpen] = useState(false);
const [infoRow, setInfoRow] = useState<ProgressRow | null>(null);
const [infoData, setInfoData] = useState<ProjectInfoData | null>(null);
// GRID_COLUMNS의 project_no 셀에만 onClick 주입 (DataGrid 컬럼별 onClick 패턴)
const columns = useMemo(
() => GRID_COLUMNS.map((col) =>
col.key === "project_no"
? { ...col, onClick: (row: any) => { setInfoRow(row as ProgressRow); setInfoOpen(true); } }
? { ...col, onClick: (row: any) => { setInfoData(toProjectInfo(row as ProgressRow)); setInfoOpen(true); } }
: col,
),
[],
@@ -240,7 +258,7 @@ export default function ProjectProgressPage() {
/>
</div>
<ProjectInfoDialog open={infoOpen} onOpenChange={setInfoOpen} row={infoRow} />
<ProjectInfoDialog open={infoOpen} onOpenChange={setInfoOpen} data={infoData} />
</div>
);
}
@@ -1,6 +1,6 @@
"use client";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -19,8 +19,27 @@ import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
import { CustomerSelect } from "@/components/common/CustomerSelect";
import { PartSelect } from "@/components/common/PartSelect";
import { CommCodeSelect } from "@/components/common/CommCodeSelect";
import { ProjectInfoDialog, ProjectInfoData } from "@/components/project/ProjectInfoDialog";
import { salesSaleApi, RevenueListRow, DeadlineInfoBody } from "@/lib/api/salesSale";
// RevenueListRow → 정규화된 ProjectInfoData (wace 운영판 다이얼로그 1:1)
const toProjectInfo = (r: RevenueListRow): ProjectInfoData => ({
orderType: r.order_type_name,
productType: r.product_type_name,
area: r.nation_name,
customer: r.customer,
paidType: r.payment_type_name,
regDate: r.receipt_date,
currency: r.contract_currency_name,
exchangeRate: r.exchange_rate,
partNo: r.product_no,
partName: r.product_name,
serialNo: r.serial_no,
reqDelDate: r.request_date,
customerRequest: r.customer_request,
returnReason: r.return_reason_name,
});
// wace_plm revenueMgmtList.jsp 컬럼 순서/라벨에 맞춤
const GRID_COLUMNS: DataGridColumn[] = [
{ key: "project_no", label: "프로젝트번호", width: "w-[170px]", frozen: true },
@@ -70,6 +89,18 @@ export default function SalesRevenuePage() {
const [loading, setLoading] = useState(false);
const [selected, setSelected] = useState<RevenueListRow | null>(null);
const [checkedIds, setCheckedIds] = useState<string[]>([]);
// 프로젝트번호 셀 클릭 → 프로젝트 상세 정보 다이얼로그 (wace 운영판 1:1)
const [infoOpen, setInfoOpen] = useState(false);
const [infoData, setInfoData] = useState<ProjectInfoData | null>(null);
const columns = useMemo(
() => GRID_COLUMNS.map((col) =>
col.key === "project_no"
? { ...col, onClick: (row: any) => { setInfoData(toProjectInfo(row as RevenueListRow)); setInfoOpen(true); } }
: col,
),
[],
);
// wace revenueMgmtList.jsp 활성 11개
const [searchForm, setSearchForm] = useState({
orderType: "", poNo: "", customer_objid: "",
@@ -268,7 +299,7 @@ export default function SalesRevenuePage() {
</div>
<DataGrid
columns={GRID_COLUMNS}
columns={columns}
data={rows}
selectedId={selected ? String(selected.log_id) : null}
onSelect={(id) => setSelected(id ? rows.find((r) => r.id === id) ?? null : null)}
@@ -280,6 +311,8 @@ export default function SalesRevenuePage() {
loading={loading}
/>
<ProjectInfoDialog open={infoOpen} onOpenChange={setInfoOpen} data={infoData} />
{/* 마감정보 입력 Dialog */}
<Dialog open={deadlineOpen} onOpenChange={setDeadlineOpen}>
<DialogContent className="max-w-3xl">
@@ -1,6 +1,6 @@
"use client";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -17,8 +17,27 @@ import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
import { CustomerSelect } from "@/components/common/CustomerSelect";
import { PartSelect } from "@/components/common/PartSelect";
import { CommCodeSelect } from "@/components/common/CommCodeSelect";
import { ProjectInfoDialog, ProjectInfoData } from "@/components/project/ProjectInfoDialog";
import { salesSaleApi, SaleListRow, SaleRegisterBody } from "@/lib/api/salesSale";
// SaleListRow → 정규화된 ProjectInfoData (wace 운영판 다이얼로그 1:1)
const toProjectInfo = (r: SaleListRow): ProjectInfoData => ({
orderType: r.order_type_name,
productType: r.product_type_name,
area: r.nation_name,
customer: r.customer,
paidType: r.payment_type_name,
regDate: r.receipt_date,
currency: r.contract_currency_name,
exchangeRate: r.exchange_rate,
partNo: r.product_no,
partName: r.product_name,
serialNo: r.serial_no,
reqDelDate: r.request_date,
customerRequest: r.customer_request,
returnReason: r.return_reason_name,
});
// wace_plm salesMgmtList.jsp 컬럼 순서/라벨에 맞춤
const GRID_COLUMNS: DataGridColumn[] = [
{ key: "project_no", label: "프로젝트번호", width: "w-[170px]", frozen: true },
@@ -75,6 +94,18 @@ export default function SalesSalePage() {
shippingDateFrom: "", shippingDateTo: "",
});
// 프로젝트번호 셀 클릭 → 프로젝트 상세 정보 다이얼로그 (wace 운영판 1:1)
const [infoOpen, setInfoOpen] = useState(false);
const [infoData, setInfoData] = useState<ProjectInfoData | null>(null);
const columns = useMemo(
() => GRID_COLUMNS.map((col) =>
col.key === "project_no"
? { ...col, onClick: (row: any) => { setInfoData(toProjectInfo(row as SaleListRow)); setInfoOpen(true); } }
: col,
),
[],
);
const [registerOpen, setRegisterOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState<SaleRegisterBody>({
@@ -248,7 +279,7 @@ export default function SalesSalePage() {
</div>
<DataGrid
columns={GRID_COLUMNS}
columns={columns}
data={rows}
selectedId={selected ? `${selected.project_no}-${selected.contract_item_objid}-0` : null}
onSelect={(id) => setSelected(id ? rows.find((r) => r.id === id) ?? null : null)}
@@ -257,6 +288,8 @@ export default function SalesSalePage() {
loading={loading}
/>
<ProjectInfoDialog open={infoOpen} onOpenChange={setInfoOpen} data={infoData} />
{/* 출하지시/판매등록 Dialog */}
<Dialog open={registerOpen} onOpenChange={setRegisterOpen}>
<DialogContent className="max-w-3xl">