diff --git a/apps/as/app/(main)/(with-base-layout)/my-pbadms-sprt/page.tsx b/apps/as/app/(main)/(with-base-layout)/my-pbadms-sprt/page.tsx new file mode 100644 index 000000000..c7b7ad4b4 --- /dev/null +++ b/apps/as/app/(main)/(with-base-layout)/my-pbadms-sprt/page.tsx @@ -0,0 +1,3 @@ +import { MyPbadmsSprtListPage } from "@/pages/my-pbadms-sprt"; + +export default MyPbadmsSprtListPage; diff --git a/apps/as/docs/spec/my-adss-doc/SPEC.md b/apps/as/docs/spec/my-adss-doc/SPEC.md new file mode 100644 index 000000000..37ae2937d --- /dev/null +++ b/apps/as/docs/spec/my-adss-doc/SPEC.md @@ -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` | `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 +``` + diff --git a/apps/as/src/entities/elapr/api/elapr-prgrs-stts-service.ts b/apps/as/src/entities/elapr/api/elapr-prgrs-stts-service.ts index f1931d556..435827cf2 100644 --- a/apps/as/src/entities/elapr/api/elapr-prgrs-stts-service.ts +++ b/apps/as/src/entities/elapr/api/elapr-prgrs-stts-service.ts @@ -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> { - const response = await client.get>(`${BASE}/my/doc`, { + params: ElaprMyDocSearchDto, +): Promise> { + const response = await client.get>(`${BASE}/my/doc`, { params, }); return response.data; diff --git a/apps/as/src/entities/elapr/api/queries.ts b/apps/as/src/entities/elapr/api/queries.ts index e93c1540e..734c6cdf4 100644 --- a/apps/as/src/entities/elapr/api/queries.ts +++ b/apps/as/src/entities/elapr/api/queries.ts @@ -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), diff --git a/apps/as/src/entities/elapr/index.ts b/apps/as/src/entities/elapr/index.ts index adec3e931..4805fecc3 100644 --- a/apps/as/src/entities/elapr/index.ts +++ b/apps/as/src/entities/elapr/index.ts @@ -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 { diff --git a/apps/as/src/entities/elapr/model/elapr-prgrs-stts-interface.ts b/apps/as/src/entities/elapr/model/elapr-prgrs-stts-interface.ts index 61d0f2cdf..1633b95f8 100644 --- a/apps/as/src/entities/elapr/model/elapr-prgrs-stts-interface.ts +++ b/apps/as/src/entities/elapr/model/elapr-prgrs-stts-interface.ts @@ -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; findMyElaperDocList( - params: ElaprPrgrsSttsMyDocSearchParams, - ): Promise>; + params: ElaprMyDocSearchDto, + ): Promise>; } diff --git a/apps/as/src/pages/my-pbadms-sprt/index.ts b/apps/as/src/pages/my-pbadms-sprt/index.ts new file mode 100644 index 000000000..02c3c6243 --- /dev/null +++ b/apps/as/src/pages/my-pbadms-sprt/index.ts @@ -0,0 +1 @@ +export { MyPbadmsSprtListPage } from "./ui/my-pbadms-sprt-list-page"; diff --git a/apps/as/src/pages/my-pbadms-sprt/lib/resolve-detail-path.ts b/apps/as/src/pages/my-pbadms-sprt/lib/resolve-detail-path.ts new file mode 100644 index 000000000..f855d4394 --- /dev/null +++ b/apps/as/src/pages/my-pbadms-sprt/lib/resolve-detail-path.ts @@ -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 }; diff --git a/apps/as/src/pages/my-pbadms-sprt/model/my-pbadms-sprt-table-columns.tsx b/apps/as/src/pages/my-pbadms-sprt/model/my-pbadms-sprt-table-columns.tsx new file mode 100644 index 000000000..2ee81a326 --- /dev/null +++ b/apps/as/src/pages/my-pbadms-sprt/model/my-pbadms-sprt-table-columns.tsx @@ -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(); + +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()), + }), + ]; +} diff --git a/apps/as/src/pages/my-pbadms-sprt/ui/my-pbadms-sprt-list-page.tsx b/apps/as/src/pages/my-pbadms-sprt/ui/my-pbadms-sprt-list-page.tsx new file mode 100644 index 000000000..a55dedbc4 --- /dev/null +++ b/apps/as/src/pages/my-pbadms-sprt/ui/my-pbadms-sprt-list-page.tsx @@ -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(defaultStartDate); + const [endDate, setEndDate] = useState(defaultEndDate); + const [docSeComCd, setDocSeComCd] = useState(""); + const [prgrsSttsComCd, setPrgrsSttsComCd] = useState(""); + const [pagination, setPagination] = useState({ + pageIndex: DEFAULT_PAGE_INDEX, + pageSize: DEFAULT_PAGE_SIZE, + }); + const [searchParams, setSearchParams] = useState(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) => { + 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 ( +
+
+
+
+ + setStartDate(date ?? null)} + className="w-[12rem]" + /> + ~ + setEndDate(date ?? null)} + className="w-[12rem]" + /> +
+ +
+ + +
+ +
+ + +
+ + +
+
+ + +
+ ); +} + +export { MyPbadmsSprtListPage }; diff --git a/apps/as/src/pages/my-pbadms-sprt/ui/my-pbadms-sprt-list.tsx b/apps/as/src/pages/my-pbadms-sprt/ui/my-pbadms-sprt-list.tsx new file mode 100644 index 000000000..29cf221c4 --- /dev/null +++ b/apps/as/src/pages/my-pbadms-sprt/ui/my-pbadms-sprt-list.tsx @@ -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 ( +
+
+

내 신청 목록

+
+ {tableComponent} +
+ ); +} + +export { MyPbadmsSprtList }; diff --git a/apps/as/src/shared/ui/page-layout.tsx b/apps/as/src/shared/ui/page-layout.tsx index 6ee9013c4..d8e34e2d6 100644 --- a/apps/as/src/shared/ui/page-layout.tsx +++ b/apps/as/src/shared/ui/page-layout.tsx @@ -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 ( -
- +
+ {children}
); diff --git a/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts b/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts index 2a936c6ad..8df2c4aca 100644 --- a/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts +++ b/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts @@ -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 };