진행관리 P1.5 재작업 — PROJECT_NO 셀 클릭 시 프로젝트 정보 다이얼로그
· wace fn_openSaleRegPopup(PROJECT_NO, "detail") 의도 재해석 — read-only 상세 조회 모드 · 행 전체 클릭(onRowClick) → PROJECT_NO 컬럼 셀 클릭(cellClick)으로 좁힘 (wace 1:1) · 판매관리 페이지 라우팅 폐기 → ProjectInfoDialog 신설 (같은 탭, list row 직접 사용, 추가 API 호출 0) · 표시 항목: 프로젝트번호/영업번호/주문유형/제품구분/국내해외/고객사/유무상/품번/품명/S/N/수주수량/접수일/요청납기/발주일/프로젝트명/작성자 · 영업관리 변경 롤백 (SaleListFilter.project_no, useSearchParams 자동 선택, 초기화 핸들러) · 01-progress / 01-progress-verify 문서 갱신 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,6 @@ export interface SaleListFilter {
|
||||
salesStatus?: string; // 판매상태 (registered/cancelled 등)
|
||||
productType?: string; // project_mgmt.product (제품구분)
|
||||
nation?: string; // project_mgmt.area_cd (국내/해외)
|
||||
// 진행관리 행 클릭으로 진입 시 (wace fn_openSaleRegPopup(PROJECT_NO) 대응)
|
||||
project_no?: string; // project_mgmt.project_no 직접 매칭
|
||||
// 매출관리 전용
|
||||
revenueMode?: string;
|
||||
salesDeadlineFrom?: string;
|
||||
@@ -73,8 +71,6 @@ export async function getSaleList(filter: SaleListFilter) {
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
// 진행관리 행 클릭 진입용 — wace fn_openSaleRegPopup(PROJECT_NO) 대응
|
||||
if (filter.project_no) { conditions.push(`T.project_no = $${idx++}`); params.push(filter.project_no); }
|
||||
if (filter.orderType) { conditions.push(`T.category_cd = $${idx++}`); params.push(filter.orderType); }
|
||||
if (filter.poNo) { conditions.push(`T.po_no ILIKE $${idx++}`); params.push(`%${filter.poNo}%`); }
|
||||
if (filter.customer_objid) { conditions.push(`T.customer_objid = $${idx++}`); params.push(filter.customer_objid); }
|
||||
|
||||
@@ -130,35 +130,34 @@ SELECT count(*) AS total,
|
||||
|
||||
각 시나리오는 `GET /api/project/progress/list?...` 로 직접 호출 또는 화면에서 검색 후 응답 행 수 / 첫 행 데이터 확인.
|
||||
|
||||
## 4. P1.5 행 클릭 라우팅 검증
|
||||
## 4. P1.5 PROJECT_NO 셀 클릭 → 프로젝트 정보 다이얼로그 검증
|
||||
|
||||
### 4.1 시나리오
|
||||
|
||||
1. `/COMPANY_16/project/progress` 진입 → 그리드 90건
|
||||
2. 임의 행(예: `S-CT-260507-003`) 클릭
|
||||
3. 브라우저 URL이 `/COMPANY_16/sales/sale?project_no=S-CT-260507-003` 로 변경
|
||||
4. 판매관리 페이지 로딩 후:
|
||||
- `searchForm.project_no` 자동 세팅
|
||||
- 그리드에 매칭 행 1건만 표시
|
||||
- 그 행이 자동 선택(`selected`) 상태
|
||||
5. "출하지시/판매등록" 버튼 활성화
|
||||
2. **PROJECT_NO 컬럼 셀만 클릭** (wace `cellClick`과 동일). 다른 컬럼 셀 클릭은 행 선택만.
|
||||
3. `ProjectInfoDialog` 오픈 — 같은 탭 내 다이얼로그 (새 창 X)
|
||||
4. 다이얼로그 내용 확인:
|
||||
- 프로젝트번호 / 영업번호 / 주문유형 / 제품구분 / 국내해외 / 고객사 / 유무상 / 품번 / 품명 / S/N / 수주수량 / 접수일 / 요청납기 / 발주일 / 프로젝트명 / 작성자
|
||||
- 모두 read-only (편집 불가)
|
||||
- 빈 값은 `-` 로 표시
|
||||
5. 닫기 버튼으로 다이얼로그 닫힘
|
||||
|
||||
### 4.2 매칭 데이터 부재 케이스
|
||||
### 4.2 별도 API 호출 없음
|
||||
|
||||
진행관리에는 있지만 판매관리(project_mgmt 메인 + sales_registration LEFT JOIN)에 매칭이 없는 경우:
|
||||
- Toast: `프로젝트번호 {PROJECT_NO} 의 판매 데이터가 없습니다.`
|
||||
- 그리드 0건 표시
|
||||
- 사용자가 검색 초기화로 전체 복귀 가능 (초기화 버튼이 `project_no`도 비움)
|
||||
list SQL 응답(`ProgressRow`)에 표시 필요 데이터가 모두 포함돼 있어 다이얼로그 오픈 시 추가 fetch 없음. 네트워크 검증:
|
||||
- DevTools Network 패널 → 셀 클릭 시 추가 `/api/...` 호출 0건 확인
|
||||
|
||||
### 4.3 backend 검증
|
||||
### 4.3 wace 운영판과의 차이
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:8080/api/project/progress/list?project_nos=PJ-1778222043948-342" \
|
||||
-H "Cookie: ..." | jq '.data | length' # 1 기대
|
||||
| 항목 | wace | RPS |
|
||||
|---|---|---|
|
||||
| 클릭 대상 | PROJECT_NO 셀 (cellClick) | 동일 |
|
||||
| 화면 형식 | 새 창 (`fn_centerPopup` 1000×550) | 같은 탭 내 Dialog |
|
||||
| 모드 | `saleNo="detail"` (판매등록 폼 detail 모드) | 프로젝트 정보 read-only |
|
||||
| 데이터 | `salesRegForm.do?orderNo=...` 서버 렌더 | list row 객체 직접 사용 (추가 API 호출 없음) |
|
||||
|
||||
curl -s "http://localhost:8080/api/sales?project_no=S-CT-260507-003" \
|
||||
-H "Cookie: ..." | jq '.data | length' # 1 기대 (판매관리에 해당 행 매칭 시)
|
||||
```
|
||||
RPS는 SPA 패턴 + 진행관리 본연의 목적이 "프로젝트 상황 모니터링"이라 정보 표시에 집중. 판매 등록은 영업관리 판매관리 메뉴에서 처리.
|
||||
|
||||
## 5. 미구현 / 알려진 갭
|
||||
|
||||
@@ -168,14 +167,6 @@ curl -s "http://localhost:8080/api/sales?project_no=S-CT-260507-003" \
|
||||
| 진척율 / 이슈 / 원가 / 출고 컬럼 데이터 | 그리드 표시엔 안 들어가지만 service SQL 자리는 0/NULL | P2(WBS) |
|
||||
| `getById` / `updateProject` 라우트 | 옛 jsp 기반 — 행 클릭 라우팅 통일 후 미사용 | 정리 검토 |
|
||||
|
||||
## 6. 회귀 방지 — 영업관리에 미친 변경
|
||||
## 6. 회귀 방지 — 영업관리에 영향 없음
|
||||
|
||||
진행관리 P1.5 라우팅을 위해 추가한 변경 — 회귀 검증 필요:
|
||||
|
||||
| 파일 | 변경 | 회귀 위험 |
|
||||
|---|---|---|
|
||||
| `backend-node/src/services/salesSaleService.ts` | `SaleListFilter.project_no` 추가, `getSaleList`에 1줄 필터 추가 | 기존 영업 검색 흐름 영향 없음 (조건 추가만) |
|
||||
| `frontend/lib/api/salesSale.ts` | `SaleListFilter.project_no` 타입만 추가 | 무영향 |
|
||||
| `frontend/app/(main)/COMPANY_16/sales/sale/page.tsx` | `useSearchParams` import + 자동 행 선택 useEffect 추가, 검색폼/초기화 핸들러에 `project_no: ""` 추가 | URL 쿼리 없으면 기존 동작과 동일 |
|
||||
|
||||
→ 영업관리 4개 메뉴 회귀 테스트 시 `/sales/sale` 정상 진입 / 조회 / 판매등록 흐름 확인.
|
||||
P1.5 초기 시도(판매관리 페이지 라우팅)는 사용자 의도와 어긋나 폐기. 영업관리에 추가했던 변경(`SaleListFilter.project_no` / `useSearchParams` 자동 선택 / 검색폼 `project_no` 필드)을 모두 롤백해 **영업관리에 변경 영향 없음**. 진행관리 P1.5는 진행관리 페이지 + 신규 `ProjectInfoDialog` 컴포넌트만으로 완결됨.
|
||||
|
||||
@@ -79,9 +79,9 @@ ORDER BY SUBSTRING(project_no, POSITION('-' IN project_no)+1) DESC,
|
||||
|---|---|---|
|
||||
| 조회 | `btnSearch` → `/projectMgmtWbsGridList.do` | `GET /api/project/progress/list` |
|
||||
| 프로젝트번호 옵션 | `code_map.project_no = common.getCusProjectNoList` | `GET /api/project/progress/project-no-options` |
|
||||
| **행 클릭** | `cellClick: fn_openSaleRegPopup(PROJECT_NO, "detail")` → `/salesMgmt/salesRegForm.do?orderNo={PROJECT_NO}` 새 창 | `router.push('/COMPANY_16/sales/sale?project_no={PROJECT_NO}')` 페이지 라우팅 + 자동 필터/선택 |
|
||||
| **PROJECT_NO 셀 클릭** (cellClick) | `fn_openSaleRegPopup(PROJECT_NO, "detail")` → `/salesMgmt/salesRegForm.do?orderNo={PROJECT_NO}&saleNo=detail` 새 창(read-only 상세 모드) | `ProjectInfoDialog` (같은 탭 다이얼로그) — 그리드 row 객체 그대로 read-only 표시 |
|
||||
|
||||
행 클릭 처리는 RPS SPA 패턴에 맞춰 새 창 대신 같은 탭 내 페이지 라우팅 + URL 쿼리로 `project_no` 전달. 판매관리 페이지가 `useSearchParams`로 받아 검색폼에 자동 적용 + 첫 매칭 행 자동 선택. wace의 의도(특정 프로젝트의 판매 등록 화면 보여주기)는 동일하게 달성.
|
||||
행 클릭은 **PROJECT_NO 컬럼 셀에만** 걸림 (wace `cellClick`). 행 전체 클릭은 그냥 행 선택만. wace의 "detail" 모드 새 창은 RPS에서는 같은 탭 내 `ProjectInfoDialog`로 매핑 — 별도 API 호출 없이 list 응답(`ProgressRow`)을 그대로 다이얼로그에 전달해 read-only 표시. 표시 항목: 프로젝트번호 / 영업번호 / 주문유형 / 제품구분 / 국내해외 / 고객사 / 유무상 / 품번 / 품명 / S/N / 수주수량 / 접수일 / 요청납기 / 발주일 / 프로젝트명 / 작성자.
|
||||
|
||||
## 3. RPS 매핑 변경 사항
|
||||
|
||||
@@ -105,12 +105,11 @@ ORDER BY SUBSTRING(project_no, POSITION('-' IN project_no)+1) DESC,
|
||||
|---|---|
|
||||
| 다중 프로젝트번호 선택 | wace는 multi-select2. RPS는 단일(`SmartSelect`). 다중 모드는 P1.5에서 보강 가능 |
|
||||
| 진척율 / 이슈 / 원가 / 출고 컬럼 채움 | 그리드 표시 컬럼엔 없으므로 영향 없음. 그러나 service SQL의 `0` 자리들은 P2에서 활성화 |
|
||||
| `getById` / `updateProject` 라우트 | 옛 jsp(projectMgmtList) 기반으로 만든 자리. 행 클릭이 판매관리 라우팅으로 통일됐으므로 미사용 — 삭제 검토 가능 |
|
||||
| `getById` / `updateProject` 라우트 | 옛 jsp(projectMgmtList) 기반 자리. 행 클릭이 `ProjectInfoDialog` (list row 직접 사용)로 통일됐으므로 미사용 — 삭제 검토 가능 |
|
||||
| 엑셀 다운로드 | wace에 없음 — 본 PR 제외 |
|
||||
|
||||
## 6. 관련 파일
|
||||
|
||||
- backend: [services/projectMgmtService.ts](../../backend-node/src/services/projectMgmtService.ts) · [controllers/projectMgmtController.ts](../../backend-node/src/controllers/projectMgmtController.ts) · [routes/projectMgmtRoutes.ts](../../backend-node/src/routes/projectMgmtRoutes.ts)
|
||||
- frontend: [app/(main)/COMPANY_16/project/progress/page.tsx](../../frontend/app/(main)/COMPANY_16/project/progress/page.tsx) · [lib/api/projectMgmt.ts](../../frontend/lib/api/projectMgmt.ts)
|
||||
- 판매관리 연동: [app/(main)/COMPANY_16/sales/sale/page.tsx](../../frontend/app/(main)/COMPANY_16/sales/sale/page.tsx)(useSearchParams `project_no`) · [services/salesSaleService.ts](../../backend-node/src/services/salesSaleService.ts)(`SaleListFilter.project_no`)
|
||||
- frontend: [app/(main)/COMPANY_16/project/progress/page.tsx](../../frontend/app/(main)/COMPANY_16/project/progress/page.tsx) · [components/project/ProjectInfoDialog.tsx](../../frontend/components/project/ProjectInfoDialog.tsx) · [lib/api/projectMgmt.ts](../../frontend/lib/api/projectMgmt.ts)
|
||||
- 검증: [01-progress-verify.md](./01-progress-verify.md)
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
// 검색폼: 11필드 (1행 6 + 2행 5)
|
||||
// 행 클릭: P1.5에서 영업관리 OrderRegistDialog 재사용 검토 — 현재 미연결
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
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";
|
||||
@@ -22,6 +21,7 @@ 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 { projectMgmtApi, ProgressListFilter, ProgressRow } from "@/lib/api/projectMgmt";
|
||||
|
||||
// wace projectMgmtWbsList3.jsp 컬럼 정의 1:1 (8그룹 → 평탄화, 그룹명은 라벨 prefix)
|
||||
@@ -75,17 +75,24 @@ const EMPTY_FILTER: ProgressListFilter = {
|
||||
};
|
||||
|
||||
export default function ProjectProgressPage() {
|
||||
const router = useRouter();
|
||||
const [rows, setRows] = useState<ProgressRow[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filter, setFilter] = useState<ProgressListFilter>(EMPTY_FILTER);
|
||||
const [projectNoOptions, setProjectNoOptions] = useState<SmartSelectOption[]>([]);
|
||||
|
||||
// wace `fn_openSaleRegPopup(PROJECT_NO)` 1:1 — RPS는 SPA 패턴 따라 판매관리 페이지로 라우팅
|
||||
const handleRowClick = (row: any) => {
|
||||
if (!row?.project_no) return;
|
||||
router.push(`/COMPANY_16/sales/sale?project_no=${encodeURIComponent(row.project_no)}`);
|
||||
};
|
||||
// wace `fn_openSaleRegPopup(PROJECT_NO, "detail")` 대응 — PROJECT_NO 셀 클릭 시 프로젝트 정보 다이얼로그
|
||||
const [infoOpen, setInfoOpen] = useState(false);
|
||||
const [infoRow, setInfoRow] = useState<ProgressRow | 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,
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -224,15 +231,16 @@ export default function ProjectProgressPage() {
|
||||
{/* 그리드 (8그룹 18셀 평탄화) */}
|
||||
<div className="flex-1 min-h-0 p-2">
|
||||
<DataGrid
|
||||
columns={GRID_COLUMNS}
|
||||
columns={columns}
|
||||
data={rows}
|
||||
loading={loading}
|
||||
showRowNumber
|
||||
onRowClick={handleRowClick}
|
||||
emptyMessage="조건에 맞는 프로젝트가 없습니다."
|
||||
gridId="project-progress-wbslist3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ProjectInfoDialog open={infoOpen} onOpenChange={setInfoOpen} row={infoRow} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -64,21 +63,16 @@ const GRID_COLUMNS: DataGridColumn[] = [
|
||||
|
||||
export default function SalesSalePage() {
|
||||
const { user } = useAuth();
|
||||
const searchParams = useSearchParams();
|
||||
// 진행관리 행 클릭으로 진입한 경우 — wace fn_openSaleRegPopup(PROJECT_NO) 대응
|
||||
const incomingProjectNo = searchParams?.get("project_no") ?? "";
|
||||
|
||||
const [rows, setRows] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selected, setSelected] = useState<SaleListRow | null>(null);
|
||||
// wace salesMgmtList.jsp 검색 폼: 1줄 7개 / 2줄 3개
|
||||
// project_no는 진입 시점에만 자동 적용 (UI에 노출 안 함)
|
||||
const [searchForm, setSearchForm] = useState({
|
||||
orderType: "", poNo: "", customer_objid: "", search_partObjId: "",
|
||||
serialNo: "", shippingStatus: "", salesStatus: "",
|
||||
orderDateFrom: "", orderDateTo: "",
|
||||
shippingDateFrom: "", shippingDateTo: "",
|
||||
project_no: incomingProjectNo,
|
||||
});
|
||||
|
||||
const [registerOpen, setRegisterOpen] = useState(false);
|
||||
@@ -102,19 +96,6 @@ export default function SalesSalePage() {
|
||||
|
||||
useEffect(() => { fetchList(); }, [fetchList]);
|
||||
|
||||
// 진행관리에서 project_no 들고 진입한 경우 — 첫 매칭 행 자동 선택
|
||||
const [autoSelectedFromUrl, setAutoSelectedFromUrl] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!incomingProjectNo || autoSelectedFromUrl || loading) return;
|
||||
if (rows.length === 0) {
|
||||
toast.warning(`프로젝트번호 ${incomingProjectNo} 의 판매 데이터가 없습니다.`);
|
||||
setAutoSelectedFromUrl(true);
|
||||
return;
|
||||
}
|
||||
setSelected(rows[0] as SaleListRow);
|
||||
setAutoSelectedFromUrl(true);
|
||||
}, [incomingProjectNo, rows, loading, autoSelectedFromUrl]);
|
||||
|
||||
const openRegister = () => {
|
||||
if (!selected) { toast.warning("판매등록할 행을 선택하세요."); return; }
|
||||
setForm({
|
||||
@@ -175,7 +156,6 @@ export default function SalesSalePage() {
|
||||
serialNo: "", shippingStatus: "", salesStatus: "",
|
||||
orderDateFrom: "", orderDateTo: "",
|
||||
shippingDateFrom: "", shippingDateTo: "",
|
||||
project_no: "",
|
||||
})}>
|
||||
초기화
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
// 진행관리 PROJECT_NO 셀 클릭 시 표시되는 프로젝트 정보 다이얼로그 (read-only).
|
||||
// wace `fn_openSaleRegPopup(PROJECT_NO, "detail")` 대응 — 새 창 대신 같은 탭 내 다이얼로그.
|
||||
// list SQL 응답(ProgressRow)에 필요 데이터가 모두 포함돼 있어 별도 detail API 호출 불필요.
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ProgressRow } from "@/lib/api/projectMgmt";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
row: ProgressRow | null;
|
||||
}
|
||||
|
||||
const fmtQty = (v: unknown) => {
|
||||
if (v == null || v === "") return "";
|
||||
const n = Number(String(v).replace(/,/g, ""));
|
||||
return isNaN(n) ? String(v) : n.toLocaleString();
|
||||
};
|
||||
|
||||
export function ProjectInfoDialog({ open, onOpenChange, row }: Props) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>프로젝트 정보</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{row ? (
|
||||
<div className="grid grid-cols-[120px_1fr] gap-y-2 gap-x-3 text-sm py-2">
|
||||
<Label>프로젝트번호</Label><Value>{row.project_no}</Value>
|
||||
<Label>영업번호</Label><Value>{row.contract_no}</Value>
|
||||
<Label>주문유형</Label><Value>{row.category_name}</Value>
|
||||
<Label>제품구분</Label><Value>{row.product_name}</Value>
|
||||
<Label>국내/해외</Label><Value>{row.area_name}</Value>
|
||||
<Label>고객사</Label><Value>{row.customer_name}</Value>
|
||||
<Label>유/무상</Label><Value>{row.free_of_charge}</Value>
|
||||
<Label>품번</Label><Value>{row.product_item_code}</Value>
|
||||
<Label>품명</Label><Value>{row.product_item_name}</Value>
|
||||
<Label>S/N</Label><Value>{row.serial_no}</Value>
|
||||
<Label>수주수량</Label><Value className="text-right">{fmtQty(row.contract_qty)}</Value>
|
||||
<Label>접수일</Label><Value>{row.reg_date}</Value>
|
||||
<Label>요청납기</Label><Value>{row.req_del_date}</Value>
|
||||
<Label>발주일</Label><Value>{row.order_date}</Value>
|
||||
<Label>프로젝트명</Label><Value>{row.project_name ?? ""}</Value>
|
||||
<Label>작성자</Label><Value>{row.writer_name}</Value>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>닫기</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function Label({ children }: { children: React.ReactNode }) {
|
||||
return <div className="text-right text-xs text-muted-foreground self-center">{children}</div>;
|
||||
}
|
||||
function Value({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
const empty = children == null || children === "";
|
||||
return (
|
||||
<div className={`min-h-7 px-2 py-1 rounded bg-muted/40 text-sm ${className ?? ""}`}>
|
||||
{empty ? <span className="text-muted-foreground/60">-</span> : children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export interface ProjectNoOption {
|
||||
export interface ProgressRow {
|
||||
objid: string;
|
||||
project_no: string | null;
|
||||
project_name: string | null;
|
||||
category_cd: string | null;
|
||||
category_name: string | null;
|
||||
customer_objid: string | null;
|
||||
|
||||
@@ -16,8 +16,6 @@ export interface SaleListFilter {
|
||||
salesStatus?: string;
|
||||
salesDeadlineFrom?: string;
|
||||
salesDeadlineTo?: string;
|
||||
// 진행관리 행 클릭으로 진입 시 query param에서 받음
|
||||
project_no?: string;
|
||||
}
|
||||
|
||||
export interface SaleListRow {
|
||||
|
||||
Reference in New Issue
Block a user