Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into deploy
Deploy Frontend / deploy (push) Successful in 3m36s

This commit is contained in:
2026-08-11 17:46:50 +09:00
13 changed files with 703 additions and 35 deletions
@@ -0,0 +1,3 @@
import { MyPbadmsSprtListPage } from "@/pages/my-pbadms-sprt";
export default MyPbadmsSprtListPage;
+285
View File
@@ -0,0 +1,285 @@
# 화면설계서: 내 행정지원
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 1.0 |
| 프로세스명 | UI설계 |
| 화면명 | 내 행정지원 |
| 화면 ID | AS_SC0204011 |
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
| 메뉴 경로 | 메인 > 행정지원서비스 > 내 행정지원 |
---
## 2. 화면 목적
로그인한 사용자가 **본인이 신청한 행정지원 문서**를 기간·구분·진행상태로 조회한다. 목록에서 행을 선택하면 해당 문서 상세 화면으로 이동한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 브레드크럼: 메인 > 행정지원서비스 > 내 행정지원 │
│ 페이지 타이틀: 내 행정지원 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 A] 검색 필터 │
│ 기간 [시작일] ~ [종료일] 구분 ▼ 진행상태 ▼ [검색] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 B] 내 신청 목록 │
│ 타이틀: 내 신청 목록 N개 │
│ ┌────┬──────┬────────┬──────────────┬────────────┬────────┬──────────────┐ │
│ │ No │ 구분 │ 신청일 │ 진행상태 │ 완료(예정)일│ 담당자 │ 담당자연락처 │ │
│ ├────┼──────┼────────┼──────────────┼────────────┼────────┼──────────────┤ │
│ │ … │ … │ … │ prgrsSttsComNm│ … │ … │ … │ │
│ └────┴──────┴────────┴──────────────┴────────────┴────────┴──────────────┘ │
│ [페이지네이션] │
└─────────────────────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
// 기간 기본값: 오늘 기준 최근 1개월 (startDate = today-1M, endDate = today)
CALL findMyElaperDocList(ElaprMyDocSearchDto + pageable)
DISPLAY 내 신청 목록 그리드 (영역 B)
ON [검색] 버튼 CLICK DO
CALL findMyElaperDocList(영역 A 조건 + page=0)
DISPLAY 내 신청 목록 그리드 (영역 B)
END ON
ON 페이지네이션 CHANGE DO
CALL findMyElaperDocList(영역 A 조건 + page/size)
DISPLAY 내 신청 목록 그리드 (영역 B)
END ON
ON 영역 B 행 CLICK DO
// docSeComCd·prgrsSttsComCd·rgtr → view/edit
NAVIGATE 문서 상세 또는 수정 화면
END ON
END
```
---
## 4. 영역 A — 검색 필터
구성: **기간 · 구분 · 진행상태** (+ 검색).
> UI설계 상세설명의 `docSecs` / `docStatusCd`는 레거시·설계 문서 명칭이다.
> **API SoT**는 `ElaprMyDocSearchDto`의 `docSeComCd` / `prgrsSttsComCd`를 사용한다.
### 4-1. 필드 정의
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| ---------- | ---------------- | ---- | --------------------------- | ----------------- | --------------------------------------------------------------------- |
| 기간(시작) | DatePicker | N | 오늘 − 1개월 (`YYYY-MM-DD`) | `startDate` | 신청일(`reqDt`) 기준 검색으로 가정 |
| 기간(종료) | DatePicker | N | 오늘 (`YYYY-MM-DD`) | `endDate` | |
| 구분 | Select(공통코드) | N | 전체(미선택) | `docSeComCd` | 상위코드 `ASDOCSE`. UI설계 `docSecs` → API `docSeComCd` |
| 진행상태 | Select(공통코드) | N | 전체(미선택) | `prgrsSttsComCd` | 상위코드 = `{docSeComCd}STTS` (예: `INSTL` → `INSTLSTTS`). UI설계 `docStatusCd` → API 필드명 |
### 4-2. 버튼
| 버튼명 | 동작 |
| ------ | ----------------------------------------------------------------- |
| 검색 | 영역 A 조건으로 `findMyElaperDocList` 호출 → 영역 B 갱신 (page=0) |
### 4-3. 콤보 의존
| 트리거 | 동작 |
| ----------- | ------------------------------------------------------------------- |
| 구분 변경 | 진행상태 옵션을 `upCd = docSeComCd + "STTS"`로 재조회. 진행상태 선택값 RESET |
| 구분 미선택 | 진행상태 비활성(옵션 비움). `upCd` 조합 불가이므로 조회하지 않음 |
---
## 5. 영역 B — 내 신청 목록
### 5-1. 그리드 컬럼
| 표시명 | Field | 비고 |
| ------------ | ---------------- | ------------------------------------------------- |
| No | (row index) | 페이지 오프셋 반영 |
| 구분 | `docSeComNm` | 코드 `docSeComCd` (`ASDOCSE`) |
| 신청일 | `reqDt` | 표시 `YYYY-MM-DD` (응답은 `yyyy-MM-dd HH:mm:ss`) |
| 진행상태 | `prgrsSttsComNm` | 텍스트 렌더링 (단계형 프로그레스 미사용) |
| 완료(예정)일 | `cmptnExpctDt` | 표시 `YYYY-MM-DD` |
| 담당자 | `picNm` | |
| 담당자연락처 | `picTelno` | |
응답에 있으나 그리드에 **표시하지 않음**:
| Field | 비고 |
| ---------------- | ----------------------------------------------------------------- |
| `docNo` | 문서번호 — 행 클릭 라우팅 키 |
| `docTtl` | 문서명/사업명 — 미표시 |
| `chgCycl` | 변경차수 — 그리드 미표시. SEWER 이동 시 query 파라미터로 사용 |
| `prgrsSttsComCd` | 진행상태 코드 — 컬럼 표시는 `prgrsSttsComNm` |
| `rgtr` | 등록자 UUID — edit 판정용 (`rgtr === userUnqId`) |
### 5-2. 목록 메타·페이지네이션
| 항목 | 매핑 |
| ------------- | ------------------------------------------- |
| 건수 `N개` | `PageResponse.totalElements` |
| page / size | `ElaprMyDocSearchDto`의 `PageableParams` |
| 페이지 컨트롤 | Spring `Page` 표준 (`number`, `totalPages`) |
### 5-3. 행 상호작용
| 동작 | 설명 |
| ------- | -------------------------------------------------------------------------------------------- |
| 행 클릭 | `docSeComCd`·`prgrsSttsComCd`·`rgtr`로 view/edit 분기 후 이동 (`resolveMyPbadmsSprtDetailPath`) |
**edit 조건** (기존 도메인 목록과 동일):
| `docSeComCd` | edit 조건 |
| ------------------------ | ---------------------------------------------------------------------- |
| `INSTL` / `FNRS` / `SEWER` | `(prgrsSttsComCd === "SV" \|\| "SL") && rgtr === loggedInUserUnqId` |
| `CNFM` | `prgrsSttsComCd === "CV" && rgtr === loggedInUserUnqId` |
| `docSeComCd` | edit 경로 | view 경로 |
| ------------ | -------------------------------------------- | --------------------------------------- |
| `FNRS` | `/fnrs-cnsltn/{docNo}/edit` | `/fnrs-cnsltn/{docNo}` |
| `INSTL` | `/instl-sncn/{docNo}/edit` | `/instl-sncn/{docNo}` |
| `CNFM` | `/design-cnstn/{docNo}/edit` | `/design-cnstn/{docNo}` |
| `SEWER` | `/sewer-bsc-plan/{docNo}/edit?chgCycl={n}` | `/sewer-bsc-plan/{docNo}?chgCycl={n}` |
| 그 외 | 이동하지 않음 | |
- SEWER `chgCycl`: `row.chgCycl ?? 1`
- 조건 불충족 시 항상 view
---
## 6. API/DTO 명세
### 6-1. 공통
| 항목 | 값 |
| --------------- | ------------------------------------------------------------------------------------------ |
| Base prefix | `/as-elapr-service/api/v1/elapr-prgrs-stts` (`ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS`) |
| 인증 | 로그인 사용자 UUID — 서버 `SecurityUtils.getLoggedInUserUnqIdUUID()` (클라이언트 미전달) |
| 클라이언트 모듈 | `@/entities/elapr` — `elaprPrgrsSttsService` / `useElaprPrgrsSttsMyDocPage` |
### 6-2. 내 행정지원 목록 — `findMyElaperDocList`
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ----------------- | ------ | --------------- | --------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------- |
| 내 신청 목록 조회 | GET | `{BASE}/my/doc` | Query: `ElaprMyDocSearchDto` + Pageable | `PageResponse<MyAdssDocResponseDto>` | `elaprPrgrsSttsService.findMyElaperDocList` / `useElaprPrgrsSttsMyDocPage` |
**Request (`ElaprMyDocSearchDto` + Pageable)**
| 필드 | 필수 | 설명 |
| ------------------------ | ---- | ------------------------------------------------------------ |
| `startDate` | N | 검색 시작일 `YYYY-MM-DD` |
| `endDate` | N | 검색 종료일 `YYYY-MM-DD` |
| `docSeComCd` | N | 문서구분 (`INSTL` / `FNRS` / `DESIGN` / `SEWER` / `PWRK` 등) |
| `prgrsSttsComCd` | N | 문서진행상태공통코드 |
| `page` / `size` / `sort` | N | Spring Pageable |
**Response (`MyAdssDocResponseDto`)**
| 필드 | 타입 | 설명 |
| ---------------- | ------- | ------------------------------------------- |
| `docNo` | string | 문서번호 |
| `docTtl` | string? | 문서명/사업명 |
| `docSeComCd` | string? | 문서구분 코드 |
| `docSeComNm` | string? | 문서구분 코드명 (`ASDOCSE`) |
| `reqDt` | string? | 신청일시 `yyyy-MM-dd HH:mm:ss` |
| `prgrsSttsComCd` | string? | 문서진행상태 코드 |
| `prgrsSttsComNm` | string? | 문서진행상태 코드명 |
| `cmptnExpctDt` | string? | 완료(예정)일 `yyyy-MM-dd` |
| `picNm` | string? | 담당자 성명 |
| `picTelno` | string? | 담당자 연락처 |
| `chgCycl` | number? | 변경차수 |
| `rgtr` | string? | 등록자 UUID |
### 6-3. 공통코드 (콤보)
| 용도 | 상위코드 | API | 비고 |
| -------- | ------------ | --------------------------- | ----------------------------- |
| 구분 | `ASDOCSE` | `useCodeList("ASDOCSE")` 등 | 기존 관리 화면과 동일 |
| 진행상태 | `{docSeComCd}STTS` | `useCodeList(docSeComCd + "STTS")` | 예: `INSTLSTTS`, `FNRSSTTS`, `SEWERSTTS`. 구분 미선택 시 미호출 |
---
## 7. UI 구현 가이드
### 7-1. 기술 스택·원칙
- UI: `@common/components` — DatePicker, Select/`CodeSelect`, Button, DataTable, Pagination
- 목록: `@tanstack/react-table` + `useDataTable` (기존 `instl-list` / `fnrs-list` 패턴)
- 데이터: React Query `useElaprPrgrsSttsMyDocPage(params)`
- 검색 조건·페이지: URL `searchParams` 동기화 권장 (`useSearchParamsState` 스킬)
- 진행상태 컬럼: `prgrsSttsComNm` 텍스트 렌더링 (단계형 프로그레스 미사용)
- 행 클릭: `docSeComCd` + `prgrsSttsComCd` + `rgtr`로 view/edit 분기 (`SEWER`는 `chgCycl` query 포함)
### 7-2. 앱·파일 배치
| 구분 | 경로 |
| ------------ | -------------------------------------------------------------- |
| 라우트 | `app/(main)/(with-base-layout)/my-pbadms-sprt/page.tsx` |
| 페이지 | `pages/my-pbadms-sprt/ui/my-pbadms-sprt-list-page.tsx` |
| 목록 그리드 | `pages/my-pbadms-sprt/ui/my-pbadms-sprt-list.tsx` |
| 경로 해석 | `pages/my-pbadms-sprt/lib/resolve-detail-path.ts` |
| 테이블 컬럼 | `pages/my-pbadms-sprt/model/my-pbadms-sprt-table-columns.tsx` |
| Entity API | `entities/elapr/api/elapr-prgrs-stts-service.ts` |
| Entity Hook | `entities/elapr/api/queries.ts` → `useElaprPrgrsSttsMyDocPage` |
| Entity Model | `entities/elapr/model/elapr-prgrs-stts-interface.ts` |
### 7-3. 영역별 UI 매핑
| 영역 | 컴포넌트 | 데이터 |
| ---- | -------------------------- | ------------------------------------------ |
| A | `my-pbadms-sprt-list-page` | 로컬 state → `ElaprMyDocSearchDto` |
| B | DataTable + columns | `useElaprPrgrsSttsMyDocPage` → `content[]` |
### 7-4. 다이얼로그·라우팅
| 항목 | 내용 |
| ----------- | -------------------------------------------------------------------- |
| 다이얼로그 | 없음 (목록 전용) |
| 목록 라우트 | `/my-pbadms-sprt` |
| 상세 이동 | §5-3 표와 동일 (view/edit + `rgtr` 소유자 검사, SEWER `chgCycl`) |
### 7-5. 데이터 흐름
```
BEGIN
// 최초 진입
SET 기간 = 최근 1개월
CALL findMyElaperDocList
DISPLAY 영역 B
ON [검색] CLICK DO
CALL findMyElaperDocList // → 영역 B 갱신
END ON
ON 페이지 CHANGE DO
CALL findMyElaperDocList
END ON
ON 영역 B 행 CLICK DO
IF NOT docNo OR unsupported docSeComCd THEN
// no-op
ELSE IF canEdit(prgrsSttsComCd, rgtr === userUnqId) THEN
NAVIGATE edit 경로 // SEWER면 ?chgCycl=
ELSE
NAVIGATE view 경로
END IF
END ON
END
```
@@ -5,11 +5,12 @@ import type { PageResponse } from "@/shared/model";
import type {
ElaprApproverChangeRequestDto,
ElaprApproverInsertRequestDto,
ElaprPrgrsSttsMyDocSearchParams,
ElaprMyDocSearchDto,
ElaprPrgrsSttsResponseDto,
ElaprPrgrsSttsSaveRequestDto,
ElaprPrgrsSttsService,
ElaprProcessRequestDto,
MyAdssDocResponseDto,
} from "../model/elapr-prgrs-stts-interface";
const BASE = ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS;
@@ -83,9 +84,9 @@ async function insertApprover(
}
async function findMyElaperDocList(
params: ElaprPrgrsSttsMyDocSearchParams,
): Promise<PageResponse<ElaprPrgrsSttsResponseDto>> {
const response = await client.get<PageResponse<ElaprPrgrsSttsResponseDto>>(`${BASE}/my/doc`, {
params: ElaprMyDocSearchDto,
): Promise<PageResponse<MyAdssDocResponseDto>> {
const response = await client.get<PageResponse<MyAdssDocResponseDto>>(`${BASE}/my/doc`, {
params,
});
return response.data;
+2 -2
View File
@@ -12,7 +12,7 @@ import type { ElaprDmndSaveRequestDto, ElaprDmndSearchDto } from "../model/elapr
import type {
ElaprApproverChangeRequestDto,
ElaprApproverInsertRequestDto,
ElaprPrgrsSttsMyDocSearchParams,
ElaprMyDocSearchDto,
ElaprPrgrsSttsSaveRequestDto,
ElaprProcessRequestDto,
} from "../model/elapr-prgrs-stts-interface";
@@ -261,7 +261,7 @@ export function useElaprPrgrsStts(elaprPrgrsId: string | null | undefined) {
});
}
export function useElaprPrgrsSttsMyDocPage(params: ElaprPrgrsSttsMyDocSearchParams) {
export function useElaprPrgrsSttsMyDocPage(params: ElaprMyDocSearchDto) {
return useQuery({
queryKey: [...elaprPrgrsSttsBaseKey, "findMyElaperDocList", params],
queryFn: () => elaprPrgrsSttsService.findMyElaperDocList(params),
+2 -1
View File
@@ -92,13 +92,14 @@ export type {
export type {
ElaprApproverChangeRequestDto,
ElaprApproverInsertRequestDto,
ElaprPrgrsSttsMyDocSearchParams,
ElaprMyDocSearchDto,
ElaprPrgrsSttsResponseDto,
ElaprPrgrsSttsSaveMode,
ElaprPrgrsSttsSaveRequestDto,
ElaprPrgrsSttsSearchDto,
ElaprPrgrsSttsService,
ElaprProcessRequestDto,
MyAdssDocResponseDto,
} from "./model/elapr-prgrs-stts-interface";
export { ELAPR_PRGRS_STTS_SAVE_MODE } from "./model/elapr-prgrs-stts-interface";
export type {
@@ -44,8 +44,30 @@ export interface ElaprPrgrsSttsSearchDto {
atrzPrgrsSttsComCd?: string;
}
/** 백엔드 ElaprPrgrsSttsSearchDto + Pageable */
export interface ElaprPrgrsSttsMyDocSearchParams extends ElaprPrgrsSttsSearchDto, PageableParams {}
/** 백엔드 MyAdssDocResponseDto */
export interface MyAdssDocResponseDto {
docNo: string;
docTtl?: string;
docSeComCd?: string;
docSeComNm?: string;
reqDt?: string;
prgrsSttsComCd?: string;
prgrsSttsComNm?: string;
cmptnExpctDt?: string;
picNm?: string;
picTelno?: string;
chgCycl?: number;
/** 등록자 UUID */
rgtr?: string;
}
/** 백엔드 ElaprMyDocSearchDto + Pageable */
export interface ElaprMyDocSearchDto extends PageableParams {
startDate?: string;
endDate?: string;
docSeComCd?: string;
prgrsSttsComCd?: string;
}
/** 백엔드 ElaprProcessRequestDto */
export interface ElaprProcessRequestDto {
@@ -86,6 +108,6 @@ export interface ElaprPrgrsSttsService {
body: ElaprApproverInsertRequestDto,
): Promise<ElaprPrgrsSttsResponseDto>;
findMyElaperDocList(
params: ElaprPrgrsSttsMyDocSearchParams,
): Promise<PageResponse<ElaprPrgrsSttsResponseDto>>;
params: ElaprMyDocSearchDto,
): Promise<PageResponse<MyAdssDocResponseDto>>;
}
@@ -0,0 +1 @@
export { MyPbadmsSprtListPage } from "./ui/my-pbadms-sprt-list-page";
@@ -0,0 +1,46 @@
import type { MyAdssDocResponseDto } from "@/entities/elapr";
function canEditByDocSe(
docSeComCd: string,
prgrsSttsComCd: string | undefined,
rgtr: string | undefined,
loggedInUserUnqId: string | undefined,
): boolean {
if (!loggedInUserUnqId || !rgtr || rgtr !== loggedInUserUnqId) return false;
if (docSeComCd === "CNFM") return prgrsSttsComCd === "CV";
if (docSeComCd === "INSTL" || docSeComCd === "FNRS" || docSeComCd === "SEWER")
return prgrsSttsComCd === "SV" || prgrsSttsComCd === "SL";
return false;
}
function resolveMyPbadmsSprtDetailPath(
row: MyAdssDocResponseDto,
loggedInUserUnqId: string | undefined,
): string | null {
const { docNo, docSeComCd, prgrsSttsComCd, rgtr, chgCycl } = row;
if (!docNo || !docSeComCd) return null;
const canEdit = canEditByDocSe(docSeComCd, prgrsSttsComCd, rgtr, loggedInUserUnqId);
switch (docSeComCd) {
case "FNRS":
return canEdit ? `/fnrs-cnsltn/${docNo}/edit` : `/fnrs-cnsltn/${docNo}`;
case "INSTL":
return canEdit ? `/instl-sncn/${docNo}/edit` : `/instl-sncn/${docNo}`;
case "CNFM":
return canEdit ? `/design-cnstn/${docNo}/edit` : `/design-cnstn/${docNo}`;
case "SEWER": {
const cycl = chgCycl ?? 1;
return canEdit
? `/sewer-bsc-plan/${docNo}/edit?chgCycl=${cycl}`
: `/sewer-bsc-plan/${docNo}?chgCycl=${cycl}`;
}
default:
return null;
}
}
export { resolveMyPbadmsSprtDetailPath };
@@ -0,0 +1,67 @@
"use client";
import { createColumnHelper } from "@tanstack/react-table";
import { dayjs } from "@/shared/lib";
import type { MyAdssDocResponseDto } from "@/entities/elapr";
const columnHelper = createColumnHelper<MyAdssDocResponseDto>();
function formatOptional(value: string | number | undefined | null) {
if (value === undefined || value === null) return "-";
const text = String(value).trim();
return text ? text : "-";
}
function formatDate(value: string | undefined) {
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
}
interface CreateMyPbadmsSprtColumnsOptions {
pageIndex: number;
pageSize: number;
}
export function createMyPbadmsSprtColumns({
pageIndex,
pageSize,
}: CreateMyPbadmsSprtColumnsOptions) {
return [
columnHelper.display({
id: "no",
header: "No",
meta: { align: "center" },
cell: ({ row }) => pageIndex * pageSize + row.index + 1,
}),
columnHelper.accessor("docSeComNm", {
header: "구분",
meta: { align: "center" },
cell: ({ row }) => formatOptional(row.original.docSeComNm || row.original.docSeComCd),
}),
columnHelper.accessor("reqDt", {
header: "신청일",
meta: { align: "center" },
cell: ({ getValue }) => formatDate(getValue()),
}),
columnHelper.accessor("prgrsSttsComNm", {
header: "진행상태",
meta: { align: "center" },
cell: ({ getValue }) => formatOptional(getValue()),
}),
columnHelper.accessor("cmptnExpctDt", {
header: "완료(예정)일",
meta: { align: "center" },
cell: ({ getValue }) => formatDate(getValue()),
}),
columnHelper.accessor("picNm", {
header: "담당자",
meta: { align: "center" },
cell: ({ getValue }) => formatOptional(getValue()),
}),
columnHelper.accessor("picTelno", {
header: "담당자연락처",
meta: { align: "center" },
cell: ({ getValue }) => formatOptional(getValue()),
}),
];
}
@@ -0,0 +1,171 @@
"use client";
import { useMemo, useState } from "react";
import type { PaginationState } from "@tanstack/react-table";
import { dayjs } from "@/shared/lib";
import { useCodeList } from "@/entities/code";
import type { ElaprMyDocSearchDto } from "@/entities/elapr";
import {
Button,
InputDatePicker,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { MyPbadmsSprtList } from "./my-pbadms-sprt-list";
const DEFAULT_PAGE_INDEX = 0;
const DEFAULT_PAGE_SIZE = 10;
const ALL_SELECT_VALUE = "__ALL__";
function defaultStartDate() {
return dayjs().subtract(1, "month").toDate();
}
function defaultEndDate() {
return dayjs().toDate();
}
function formatDateParam(date: Date | null | undefined) {
return date ? dayjs(date).format("YYYY-MM-DD") : "";
}
function buildDefaultSearchParams(): ElaprMyDocSearchDto {
return {
startDate: formatDateParam(defaultStartDate()),
endDate: formatDateParam(defaultEndDate()),
docSeComCd: "",
prgrsSttsComCd: "",
page: DEFAULT_PAGE_INDEX,
size: DEFAULT_PAGE_SIZE,
};
}
function MyPbadmsSprtListPage() {
const [startDate, setStartDate] = useState<Date | null>(defaultStartDate);
const [endDate, setEndDate] = useState<Date | null>(defaultEndDate);
const [docSeComCd, setDocSeComCd] = useState("");
const [prgrsSttsComCd, setPrgrsSttsComCd] = useState("");
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: DEFAULT_PAGE_INDEX,
pageSize: DEFAULT_PAGE_SIZE,
});
const [searchParams, setSearchParams] = useState<ElaprMyDocSearchDto>(buildDefaultSearchParams);
const { data: docSeCodes, isPending: isDocSeLoading } = useCodeList("ASDOCSE");
const prgrsSttsUpCd = docSeComCd ? `${docSeComCd}STTS` : undefined;
const { data: prgrsSttsCodes, isPending: isPrgrsSttsLoading } = useCodeList(prgrsSttsUpCd);
const isPrgrsSttsDisabled = !docSeComCd || isPrgrsSttsLoading;
const prgrsSttsOptions = useMemo(
() => (docSeComCd ? (prgrsSttsCodes ?? []) : []),
[docSeComCd, prgrsSttsCodes],
);
const handleDocSeChange = (value: string) => {
const nextDocSeComCd = value === ALL_SELECT_VALUE ? "" : value;
setDocSeComCd(nextDocSeComCd);
setPrgrsSttsComCd("");
};
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
setSearchParams({
startDate: formatDateParam(startDate),
endDate: formatDateParam(endDate),
docSeComCd,
prgrsSttsComCd,
page: DEFAULT_PAGE_INDEX,
size: pagination.pageSize,
});
};
return (
<div className="space-y-4 px-[1.8rem] pb-6">
<section className="card card-border mx-0 bg-background">
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
<div className="flex items-center gap-2">
<Label className="whitespace-nowrap">기간</Label>
<InputDatePicker
value={startDate}
onChange={(date) => setStartDate(date ?? null)}
className="w-[12rem]"
/>
<span className="text-muted-foreground">~</span>
<InputDatePicker
value={endDate}
onChange={(date) => setEndDate(date ?? null)}
className="w-[12rem]"
/>
</div>
<div className="flex items-center gap-2">
<Label htmlFor="docSeComCd" className="whitespace-nowrap">
구분
</Label>
<Select
value={docSeComCd || ALL_SELECT_VALUE}
onValueChange={handleDocSeChange}
disabled={isDocSeLoading}
>
<SelectTrigger id="docSeComCd" className="w-[12rem]">
<SelectValue placeholder={isDocSeLoading ? "로딩 중…" : "전체"} />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
{docSeCodes?.map((item) => (
<SelectItem key={item.lwpoCd} value={item.lwpoCd}>
{item.lwpoCdNm}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<Label htmlFor="prgrsSttsComCd" className="whitespace-nowrap">
진행상태
</Label>
<Select
value={prgrsSttsComCd || ALL_SELECT_VALUE}
onValueChange={(value) =>
setPrgrsSttsComCd(value === ALL_SELECT_VALUE ? "" : value)
}
disabled={isPrgrsSttsDisabled}
>
<SelectTrigger id="prgrsSttsComCd" className="w-[12rem]">
<SelectValue placeholder={docSeComCd ? "전체" : "구분 선택"} />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
{prgrsSttsOptions.map((item) => (
<SelectItem key={item.lwpoCd} value={item.lwpoCd}>
{item.lwpoCdNm}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button type="submit">검색</Button>
</form>
</section>
<MyPbadmsSprtList
searchParams={searchParams}
pagination={pagination}
onPaginationChange={setPagination}
/>
</div>
);
}
export { MyPbadmsSprtListPage };
@@ -0,0 +1,89 @@
"use client";
import { useMemo } from "react";
import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useDataTable, useUserStore } from "@/shared/lib";
import {
type ElaprMyDocSearchDto,
type MyAdssDocResponseDto,
useElaprPrgrsSttsMyDocPage,
} from "@/entities/elapr";
import { resolveMyPbadmsSprtDetailPath } from "../lib/resolve-detail-path";
import { createMyPbadmsSprtColumns } from "../model/my-pbadms-sprt-table-columns";
interface MyPbadmsSprtListProps {
searchParams: ElaprMyDocSearchDto;
pagination: PaginationState;
onPaginationChange: (pagination: PaginationState) => void;
}
function toOptionalParam(value: string | undefined) {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
function MyPbadmsSprtList({ searchParams, pagination, onPaginationChange }: MyPbadmsSprtListProps) {
const router = useRouter();
const userInfo = useUserStore((state) => state.userInfo);
const queryParams = useMemo(
(): ElaprMyDocSearchDto => ({
startDate: toOptionalParam(searchParams.startDate),
endDate: toOptionalParam(searchParams.endDate),
docSeComCd: toOptionalParam(searchParams.docSeComCd),
prgrsSttsComCd: toOptionalParam(searchParams.prgrsSttsComCd),
page: pagination.pageIndex,
size: pagination.pageSize,
sort: searchParams.sort,
}),
[searchParams, pagination],
);
const pageQuery = useElaprPrgrsSttsMyDocPage(queryParams);
const columns = useMemo(
() =>
createMyPbadmsSprtColumns({
pageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
}),
[pagination.pageIndex, pagination.pageSize],
);
const handleRowClick = (row: MyAdssDocResponseDto) => {
const path = resolveMyPbadmsSprtDetailPath(row, userInfo?.userUnqId);
if (path) router.push(path);
};
const { tableComponent } = useDataTable({
query: pageQuery,
tableOptions: {
columns,
state: { pagination },
onPaginationChange: (updater) => {
const next = typeof updater === "function" ? updater(pagination) : updater;
onPaginationChange(next);
},
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
viewPagination: true,
onRowClick: handleRowClick,
},
});
return (
<section className="card card-border mx-0 bg-background pb-8 pt-0">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-semibold">내 신청 목록</h3>
</div>
{tableComponent}
</section>
);
}
export { MyPbadmsSprtList };
+3 -8
View File
@@ -5,16 +5,11 @@ import { useMenuStore } from "../lib/use-menu-store";
function PageLayout({ children }: { children: React.ReactNode }) {
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
const menuPaths = currentMenuInfo?.menuPath
?.split(">")
.map((path) => path.trim());
const menuPaths = currentMenuInfo?.menuPath?.split(">").map((path) => path.trim());
return (
<div className="bg-container h-auto min-h-full pb-2">
<PageTitle
title={currentMenuInfo?.menuKornNm ?? ""}
menuPaths={menuPaths ?? []}
/>
<div className="h-auto min-h-full bg-container pb-2">
<PageTitle title={currentMenuInfo?.menuKornNm ?? ""} menuPaths={menuPaths ?? []} />
{children}
</div>
);
@@ -8,10 +8,6 @@ interface ProgressStatusItem {
regDt: string;
}
const TECH_REVIEW_STTS_COM_CD = "TE";
const TECH_REVIEW_ATRZ_SE_COM_CD = "ATRZ02";
const TECH_REVIEW_ATRZ_PRGRS_STTS_COM_CD = "ATRZ_PRGRS_003";
function findProgressStatusItem(
sttsChgHstryList: DocSttsChgHstryResponseDto[],
sttsComCd: string,
@@ -28,11 +24,7 @@ function findTechReviewProgressItem(
elaprDocPrgrsList: ElaprDocPrgrsClientResponseDto[],
): ProgressStatusItem[] {
return elaprDocPrgrsList
.filter(
(item) =>
item.atrzSeComCd === TECH_REVIEW_ATRZ_SE_COM_CD &&
item.atrzPrgrsSttsComCd === TECH_REVIEW_ATRZ_PRGRS_STTS_COM_CD,
)
.filter((item) => item.atrzSeComCd === "ATRZ02" && item.atrzPrgrsSttsComCd === "ATRZ_PRGRS_003")
.map((item) => ({
rgtrNm: item.aprvrNm ?? "",
regDt: item.atrzDt ?? "",
@@ -44,16 +36,11 @@ function resolveProgressStatusItem(
sttsChgHstryList: DocSttsChgHstryResponseDto[],
elaprDocPrgrsList: ElaprDocPrgrsClientResponseDto[],
): ProgressStatusItem[] {
if (sttsComCd === TECH_REVIEW_STTS_COM_CD) {
if (sttsComCd === "TE") {
return findTechReviewProgressItem(elaprDocPrgrsList);
}
return findProgressStatusItem(sttsChgHstryList, sttsComCd);
}
export {
findProgressStatusItem,
findTechReviewProgressItem,
resolveProgressStatusItem,
TECH_REVIEW_STTS_COM_CD,
};
export { findProgressStatusItem, findTechReviewProgressItem, resolveProgressStatusItem };