Compare commits

..
Author SHA1 Message Date
chanii8 60ee194cc9 [common/components] design: NoticePopover 스타일 수정 2026-09-23 13:55:16 +09:00
chanii8 3d027ed374 [common/features] chore: 게시판 댓글 사용 여부 적용 2026-09-23 11:37:16 +09:00
chanii8 d70eb17a72 Merge branch 'iipbs/as/dev' 2026-09-23 09:50:30 +09:00
chanii8 ddd619babc [as] feat: 행정지원 문서 체크리스트 레이블 변경 2026-09-23 09:48:08 +09:00
chanii8 5d27ee830e [as] feat: 행정지원 문서 체크리스트 탭 노출 알고리즘 수정 2026-09-23 09:35:35 +09:00
mind-one005 212b9a43a9 Merge branch 'iipbs/wr/dev' 2026-09-22 20:49:40 +09:00
mind-one005 f092a23ee4 wr: font 크기 조정 2026-09-22 20:45:22 +09:00
chanii8 25c357a423 [as] chore: 보완요청 섹션 첨부파일 관련 코드 수정 2026-09-22 16:21:13 +09:00
chanii8 a77246a1db [as] fix: 타입 에러 2026-09-22 15:39:06 +09:00
chanii8 f00ce31648 [as] refactor: zod 스키마 .optional()-> .nullish() 변경 2026-09-22 15:12:28 +09:00
chanii8 dc57a491dd [as] chore: 재원협의 코드 수정 2026-09-22 15:02:36 +09:00
mind-one005 c9b7266959 wr: 재이용실적 수정 2026-09-22 11:24:32 +09:00
chanii8 0dd2111cb9 [as] refactor: 재원협의 폼 useWatch + Controller 방식 제거 2026-09-22 10:57:41 +09:00
mind-one005 c39b048398 Merge branch 'iipbs/wr/dev' 2026-09-22 10:25:49 +09:00
chanii8 87fbf15e2b [as] feat: 재원협의 수정 2026-09-22 10:23:10 +09:00
mind-one005 db1cfaa1a6 wr: label 위치 조정 2026-09-22 10:05:49 +09:00
mind-one005 905b750877 Merge branch 'iipbs/sn/dev' 2026-09-22 09:28:50 +09:00
mind-one005 be14fe2603 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-22 09:06:39 +09:00
mind-one005 d7f101ee30 wr: 시도,시군구 전체"0" 2건 조회현상 수정 2026-09-21 17:51:09 +09:00
chanii8 20f7d595a1 Merge branch 'iipbs/as/dev' 2026-09-21 17:50:59 +09:00
chanii8 e909dc81ad [as] chore: 재원협의 버튼 노출 조건 수정 2026-09-21 17:50:23 +09:00
chanii8 a510779ea1 [as] feat: AI 만족도 평가 기능 구현 2026-09-21 17:24:01 +09:00
chanii8 0818e9ade2 Merge branch 'iipbs/as/dev' 2026-09-21 16:57:46 +09:00
chanii8 e60a8bed8b fix: 빌드 에러 2026-09-21 16:57:16 +09:00
chanii8 17fdcd7d1f fix: 빌드 에러 2026-09-21 16:56:46 +09:00
chanii8 6412e1a997 Merge branch 'iipbs/as/dev' 2026-09-21 16:36:58 +09:00
chanii8 1674598480 [as] design: RHFDevtool 스타일 수정 2026-09-21 16:36:28 +09:00
chanii8 23ebf36244 [as] feat: "@hookform/devtools" 추가 2026-09-21 16:04:19 +09:00
chanii8 342db06685 [as] refactor: 행정지원 문서 저장 payload 스키마 수정 2026-09-21 15:18:42 +09:00
mind-one001 35b9f813c4 로그삭제 2026-09-21 15:08:07 +09:00
mind-one005 aafb326662 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-21 14:46:02 +09:00
mind-one005 aa1b309b26 wr: 중수도 단위, 모달 조회 순서, 시설구분 조회 2026-09-21 14:45:50 +09:00
100JM 2c355525df [il] - DB 표준화로 인한 소스 수정 2026-09-21 13:55:37 +09:00
chanii8 bd20a499d3 [as] feat: AI 수동 검증 버튼 추가 2026-09-21 13:34:26 +09:00
chanii8 5fec5d5239 [as] feat: RAG 인덱싱 상태일 때 검토자 지정 버튼 분기 처리 2026-09-21 11:33:59 +09:00
chanii8 a14b22b1ef Merge branch 'iipbs/as/dev' 2026-09-21 11:02:55 +09:00
chanii8 50aa549311 [as] feat: 공단 실무협의서 관리 버튼 노출 조건 수정 2026-09-21 11:02:22 +09:00
kiminchan bf0aef59cd Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/dev_kic 2026-09-21 10:06:26 +09:00
kiminchan b2ddcd0cf3 sn : 통수위험 결과 안내문에 위험 판정기준 및 상태 색상 표시 2026-09-21 09:55:55 +09:00
chanii8 572ef04bb4 Merge branch 'iipbs/as/dev' 2026-09-18 17:37:31 +09:00
chanii8 110d6c689c [as] feat: UploadedFileChip 컴포넌트 props 추가(onDownload) 2026-09-18 17:33:45 +09:00
chanii8 f40d6fd5e8 [as] feat: AttachmentDownloadLink 컴포넌트 구현 및 적용 2026-09-18 16:51:00 +09:00
mind-one001 49ace44c9b 테스트 파일 삭제 2026-09-18 16:35:21 +09:00
chanii8 cc3bc548c9 [as] feat: 재원협의 코드 수정 2026-09-18 16:27:51 +09:00
chanii8 cc3d7ccd7b Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-09-17 17:05:08 +09:00
chanii8 43bc7db7d5 [as] chore: useAplnRows 수정 2026-09-17 17:04:31 +09:00
chanii8 b0672aead5 Merge branch 'iipbs/as/dev' 2026-09-17 16:48:50 +09:00
chanii8 086d5a18e8 [as] chore: 재원협의 코드 수정 2026-09-17 16:48:13 +09:00
mind-one005 91576f401b sn: 침수예측오버레이 수정 2026-09-17 15:41:08 +09:00
mind-one005 febcd18eda Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-17 14:05:11 +09:00
mind-one005 8cc9f0d291 sn: 침수예측현황 침수위험도 수정 2026-09-17 14:04:51 +09:00
chanii8 41643563f8 [as] chore: 수정사항 반영 2026-09-17 10:55:51 +09:00
chanii8 b6b58eaeba feat: 재원협의&실무협의서 로직 사이클 2026-09-17 10:00:00 +09:00
chanii8 e8a5701e73 [as] feat: 웹기안기 다이얼로그 내 금액검증결과 섹션 구현 2026-09-16 17:37:44 +09:00
kiminchan a8f2c8f41a GIS 편집 신규 관로 미리보기 표시 보완 2026-09-16 17:32:02 +09:00
100JM 241222dcd1 [il] - 연계 db 표준화로 인한 소스 수정 2026-09-16 14:52:38 +09:00
chanii8 380575dae0 [as] feat: 실무협의서 검토 로직(RR, RK) 작업 2026-09-16 14:19:39 +09:00
mind-one001 4d685d5a67 Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/dev 2026-09-16 11:15:30 +09:00
mind-one001 96196229f4 fix(sn): align business form limits with standardized database 2026-09-16 11:15:18 +09:00
chanii8 5e54039028 [as] feat: 실무협의서 검토자 지정(EV) 로직 작업 2026-09-15 16:42:21 +09:00
mind-one005 94b2e84c10 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-15 16:28:23 +09:00
mind-one001 80d2230c9a 불필요 파일 삭제 2026-09-15 10:00:42 +09:00
chanii8 4d20a68f93 Merge branch 'iipbs/as/dev' 2026-09-14 17:52:41 +09:00
chanii8 90a1d6fdff [as] feat: 웹기안기 '열기' 기능 추가(개발용) 2026-09-14 17:52:16 +09:00
chanii8 00e1cb707e [as] feat: 재원협의 환경청 조치결과 검토 의뢰(RA) 로직 작업 2026-09-14 17:35:00 +09:00
chanii8 6eeb774a21 [as] feat: 재원협의 지자체 조치완료(RB) 로직 작업 2026-09-14 13:36:23 +09:00
chanii8 6c4b8a52dc [as] chore: 보완요청 사항 적용 2026-09-14 10:16:16 +09:00
chanii8 a7e0d8f826 [as] feat: 첨부파일 컴포넌트 생성 및 적용 2026-09-11 17:23:45 +09:00
chanii8 d1ccddc1d3 [as] feat: 재원협의 기술검토 로직 작업 2026-09-11 17:02:09 +09:00
mind-one005 d5f0c3bc95 sn: 침수발생이력, 침수예측현황 팝업크기 변경 외 2026-09-11 11:01:55 +09:00
chanii8 056643bd96 Merge branch 'iipbs/as/dev' 2026-09-11 10:36:03 +09:00
chanii8 01d5ed297e [as] chore: 재원협의 상세화면 게이트 수정 2026-09-11 10:35:33 +09:00
chanii8 dae5d0a9f5 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-09-10 17:52:46 +09:00
chanii8 41ad33310a [as] feat: 실무협의서 관리 다이얼로그 내 첨부파일 섹션 추가 2026-09-10 17:50:56 +09:00
chanii8 b38ee9c7c7 Merge branch 'iipbs/as/dev' 2026-09-10 15:43:30 +09:00
chanii8 9f7f1db9b8 [as] feat: 기안기 저장 코드 수정 2026-09-10 14:50:09 +09:00
mind-one001 b7134cb3e4 노후관로현황 그리드 수정 2026-09-10 14:06:24 +09:00
mind-one001 ec78658e46 시도별 노후관로현황 그리드 수정 2026-09-10 14:01:58 +09:00
mind-one001 44361af392 그리드 css 통합 2026-09-10 13:51:42 +09:00
mind-one001 76130caa6d Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-10 11:30:58 +09:00
mind-one001 352a78a0c5 gis 업로드 오류 기능 수정 2026-09-10 11:30:52 +09:00
chanii8 a2a165a42b Merge branch 'iipbs/as/dev' 2026-09-09 18:08:35 +09:00
chanii8 0f27c1e5ce [as] chore: 웹기안기 다이얼로그 수정 2026-09-09 18:08:08 +09:00
chanii8 e944ad4369 [as] feat: 실무협의서 웹기안기에 렌더링 2026-09-09 17:38:40 +09:00
kiminchan 65bee9586d 도시침수지도 목록 이동 운영 보완 기록 2026-09-09 16:45:12 +09:00
mind-one001 31cda8579a Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-09 15:42:53 +09:00
mind-one001 1dc40b9efc 빈 레이어 트리로 지도만 재사용하는 화면에서는 패널 외곽과 제목도 표시하지 않는다. 2026-09-09 15:42:31 +09:00
mind-one005 6cb3d8ad65 Merge branch 'iipbs/wr/dev' into iipbs/sn/dev 2026-09-09 14:06:31 +09:00
LEEKYUHYUN 225e2732d1 사이드패널 패딩 조정 및 스크롤바 수정 2026-09-09 11:35:59 +09:00
chanii8 768ad001d2 [as] feat: 재원협의 쿼리 무효화 로직 수정 2026-09-09 10:30:29 +09:00
mind-one005 866d11988d Merge branch 'iipbs/sn/dev' 2026-09-09 09:27:53 +09:00
mind-one005 d82ae99b6a sn: 오류 수정 2026-09-09 09:09:09 +09:00
mind-one005 eb4f6f1b09 Merge branch 'iipbs/sn/dev' 2026-09-08 22:29:39 +09:00
mind-one005 23341b93c7 sn: 침수흔적심선/위선 화살표 연속 scroll 추가 2026-09-08 18:15:40 +09:00
mind-one005 b99f4904b0 sn: featureClickEventPopup에서 침수흔적도 관련 분리 2026-09-08 17:13:36 +09:00
chanii8 17b1c12286 Merge branch 'iipbs/st/dev' 2026-09-08 17:01:32 +09:00
chanii8 c79824e358 [as] feat: 보완요청 파일 첨부 로직 수정 2026-09-08 17:01:02 +09:00
mind-one005 f742feca4b sn: 상단 주석 외 침수흔적도 관련 수정 2026-09-08 15:12:40 +09:00
rsone001 de8971bea6 [ST] fix: 대시보드 통계 처리 현황 panel css 수정 2026-09-08 13:43:26 +09:00
mind-one005 569e003827 sn: 팝업 화면 여백 조정 2026-09-08 11:01:53 +09:00
chanii8 c47c130fc3 [as] feat: 파일보완 요청 기능 구현 2026-09-08 10:29:27 +09:00
mind-one005 2db5dd59af Merge branch 'iipbs/wr/dev' into iipbs/sn/dev 2026-09-07 18:15:04 +09:00
mind-one005 8a8504dca9 sn: 운영정보 버튼 위치 수정 2026-09-07 17:51:36 +09:00
LEEKYUHYUN 7e82c6a802 "디지털하수관로" UI 수정 2026-09-07 17:44:46 +09:00
chanii8 01377b6013 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-09-07 17:26:39 +09:00
chanii8 b38be38169 [as] feat: AI 자동 진행 단계 대기 상태 텍스트 추가 2026-09-07 17:25:40 +09:00
chanii8 b9ac4a1c18 [as] feat: 재원협의 서비스(fnrsCnsltnService) 수정 2026-09-07 17:05:59 +09:00
mind-one005 bdc6251474 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-07 17:01:46 +09:00
mind-one005 479cf5c4a0 sn: 다단header 묶음 처리 외 2026-09-07 16:56:35 +09:00
chanii8 dbea497cc5 [as] feat: 웹한글 기안기 데모 페이지 생성 2026-09-07 15:24:06 +09:00
chanii8 94af73a810 [as] docs: 실무협의 로직 개발 가이드 추가 2026-09-07 15:22:21 +09:00
rsone001 a7fd6a8e16 [ST] - fix: 취약점 점검 관련 조치 2026-09-07 14:26:00 +09:00
mind-one001 2b3502161c 시설물 상세창 깨짐 수정 2026-09-04 17:16:32 +09:00
rsone001 8039ab8281 [ST] - refactor: 2-5 맨홀 지표 변경된 테이블에 맞게 수정, 일반검증 항목 입력기간일 시 등록/수정 불가 2026-09-04 16:05:26 +09:00
mind-one001 0544a72011 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-04 15:27:18 +09:00
mind-one001 34ba09ce1b 준설관리 기능 보완 2026-09-04 15:24:17 +09:00
chanii8 2bfe653ac0 [as] feat: 'dcrag-to' API 구현 2026-09-04 15:18:32 +09:00
mind-one005 aa5a71ae68 sn: 침입수, RDII 제목란 조금 넓게 외 2026-09-04 14:52:11 +09:00
mind-one005 9067e645b3 sn: 정보검색 기본정보 구분란 화면이 줄었을 때, 디자인 외 2026-09-04 14:33:25 +09:00
mind-one005 10d3fc096a sn: 시도 전체일 때, 시군구 목록 비우기 2026-09-04 11:00:38 +09:00
mind-one005 7fd95ba6f9 sn: src/shared/util/util.ts 적용 외 2026-09-04 10:32:36 +09:00
kiminchan b4982e07b8 Merge branch 'iipbs/sn/dev' 2026-09-04 10:22:18 +09:00
jcchoi 351e1091ce Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 5m9s
2026-09-03 18:23:16 +09:00
mind-one005 e707637c88 수정 2026-09-03 17:10:11 +09:00
mind-one005 5da89e8ee7 sn: appendApiSearchParams() 함수 적용 2026-09-03 17:09:58 +09:00
rsone001 794691f05e [ST] - fix: 제출결과(검증)화면에 시설명 컬럼 추가 2026-09-03 16:29:40 +09:00
rsone001 e58b84a01b [ST] - fix: input 제출 - 시계열 검증 요청 response & request 수정, 대시보드 구성 수정 2026-09-03 15:27:19 +09:00
rsone001 3d51f003c0 Merge remote-tracking branch 'origin/feature/sein' into iipbs/st/dev 2026-09-03 13:52:34 +09:00
yangsein b5a99e5f59 [feat] 대쉬 보드 일정관리 제거 , 통계 현황상태 추가 2026-09-03 13:44:09 +09:00
rsone001 afca685fd4 [ST] - fix: 2-3 매설연장 관리자일시 수정 가능하도록 적용, SearchFrom 로직 수정 2026-09-03 11:26:23 +09:00
mind-one005 f9ff279895 Merge branch 'iipbs/sn/dev' into iipbs/wr/dev 2026-09-02 18:18:01 +09:00
mind-one005 4cde9b67c1 sn: util.ts 추가 정리 2026-09-02 18:10:50 +09:00
rsone001 261941452a [ST] fix - 2-2 지표 컬럼 설정 및 저장 제출 로직 수정 2026-09-02 16:41:12 +09:00
chanii8 a860650dfb Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-09-02 16:24:39 +09:00
chanii8 63d2dd1f4f [as] feat: 결재선 지정 API 파라미터 추가 2026-09-02 11:54:52 +09:00
kiminchan 8d765c2825 Merge branch 'iipbs/sn/dev' 2026-09-02 09:43:30 +09:00
mind-one005 80d93c1631 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-09-01 18:14:16 +09:00
mind-one005 872b3d08c0 sn: 선택 레이어 전체 디자인 수정 2026-09-01 18:10:31 +09:00
chanii8 47cb11d07b Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-09-01 17:19:02 +09:00
rsone001 8576ef83b5 [ST] - refactor: 6-1 중심처리시설 row는 위탁계약기간 직접 수정 못하도록 적용 2026-09-01 16:25:55 +09:00
kiminchan cb191817a8 Merge branch 'iipbs/sn/dev' into iipbs/sn/dev_kic 2026-09-01 16:01:37 +09:00
kiminchan 8198fda45b 공통코드 조회 기반 업무 화면 표시 적용 2026-09-01 15:55:02 +09:00
mind-one005 37374cb754 sn: 선택 레이어 설정 - 침수흔적도 2026-09-01 15:38:25 +09:00
rsone001 e80b8953a0 [ST] - fix: 일반검증 수식 사용자 직접입력 등록시 공백제거 2026-09-01 14:41:49 +09:00
mind-one005 9e36edec7f sn: 침수흔적위선/심선 정보 닫을 때 지도처리 2026-09-01 11:10:27 +09:00
mind-one005 3204bba71b Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-31 23:17:20 +09:00
mind-one005 3cdcf6a6f7 sn: 침수흔적위선 심선/위선 모두 나오도록 2026-08-31 23:16:57 +09:00
chanii8 1d1141b59a Merge branch 'iipbs/as/dev' 2026-08-31 17:22:46 +09:00
chanii8 8fd1d59fe2 [as] feat: 파일 양식 다운로드 API 수정 2026-08-31 17:18:39 +09:00
kiminchan 922861c872 통수위험 행 선택 시 초단기예보격자망 자동 활성화 2026-08-31 13:42:57 +09:00
mind-one005 4a3945f1e4 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-31 11:36:15 +09:00
mind-one005 a1caa1b860 sn: .env.local 원복 2026-08-31 11:36:05 +09:00
mind-one005 166dea2b86 sn: 침수흔적도 layer 수정 2026-08-31 11:34:19 +09:00
kiminchan 3f7262e213 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-31 11:06:24 +09:00
kiminchan 231ee10ff8 조사사업 양식 버튼 병합 충돌 오류 수정 2026-08-31 11:05:22 +09:00
mind-one005 5aa9ab4f39 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-31 11:00:02 +09:00
kiminchan 8c46f7ca06 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-31 10:46:38 +09:00
kiminchan 23e15daa37 조사사업 등록·수정 양식 다운로드 흐름 보완 2026-08-31 10:35:05 +09:00
mind-one005 dd56ed60ad sn: 용어 변경 - 수위, 깊이 > 위선,심선 2026-08-31 10:23:37 +09:00
mind-one005 18129905d8 sn: 연도버튼 및 침수흔적깊이 2026-08-29 11:21:26 +09:00
chanii8 f54ce13711 Merge branch 'iipbs/as/dev' 2026-08-28 17:44:06 +09:00
chanii8 fb12eadf65 [as] feat: 필수첨부파일 섹션 수정(파일 양식 컬럼) 2026-08-28 17:43:47 +09:00
chanii8 bb3179f0e4 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-08-28 17:12:54 +09:00
rsone001 e8b309460e [ST] input 특광역시 로직 수정, 일반검증 엑셀 다운로드 추가, 모든 number 타입 컬럼에 footer 추가 2026-08-28 17:08:20 +09:00
chanii8 ed0736dcde Merge branch 'iipbs/as/dev' 2026-08-28 17:06:34 +09:00
chanii8 edc5bb52af Merge branch 'iipbs/st/dev' 2026-08-28 17:06:20 +09:00
chanii8 37ac927c45 [st] feat: 시계열 검토 이력 > 지자체 진입 시 시도/시군구 자동 선택 2026-08-28 17:05:40 +09:00
mind-one005 d03263a3c6 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-28 16:48:17 +09:00
mind-one005 be46bcbd17 sn: 침수흔적깊이 정보 추가 2026-08-28 16:08:47 +09:00
chanii8 bf9a34cbe8 [st] feat: 하수도 통계 검증 > 시도/시군구 셀렉트에 '전체' 옵션 추가 2026-08-28 15:55:05 +09:00
mind-one001 201dbf577f 관망도 업로드 수정 2026-08-28 15:45:43 +09:00
chanii8 fc961e2946 [as] feat: 필수첨부파일관리 등록 다이얼로그 > 양식파일 첨부 로직 수정 2026-08-28 15:14:44 +09:00
chanii8 e401455f31 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-08-28 14:32:59 +09:00
chanii8 254986b48b [as] 필수펌부파일관리 내 양식파일 컬럼 추가 2026-08-28 14:31:54 +09:00
chanii8 4c1615853c [as] docs: 실무협의서 개발가이드 문서 추가 2026-08-28 11:11:32 +09:00
mind-one001 884dd71ca7 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev
# Conflicts:
#	apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx
2026-08-28 10:00:36 +09:00
mind-one001 70efdc9db5 유하방향 표출 수정 2026-08-28 09:52:41 +09:00
mind-one005 36549e7281 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-27 22:34:15 +09:00
mind-one005 c1ffc33834 sn: 침수예측지도 오버레이 & 침수흔적깊이 2026-08-27 22:33:37 +09:00
chanii8 f67e065c5e Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-27 17:23:40 +09:00
rsone001 18a11ab5fa [ST] - refactor: 입력화면 전용 시도 조회 api 추가 2026-08-27 17:22:34 +09:00
chanii8 6a7eb1f584 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-27 17:17:25 +09:00
rsone001 214ff1a435 [ST] - fix: 일반 검증 타입 총계 검증일 시 group by 항목 렌더링 되지 않게 수정 2026-08-27 15:44:45 +09:00
rsone001 d7cc579335 [ST] - refactor: 지난검증결과 불러오기 필드 추가(시설명) 2026-08-27 15:32:12 +09:00
mind-one005 2c3961644c sn: 침수예측지도 오버레이 표시1 2026-08-27 14:10:44 +09:00
rsone001 9b53c1ae08 [ST] - fix: 일반검증 항목 관리, 참조 지표(컬럼) 오류 로직 수정 2026-08-27 13:07:33 +09:00
chanii8 4840a95097 Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-27 11:31:13 +09:00
chanii8 08a7a4d6bc [as] feat: 재원협의 내 실무협의서 관련 버튼 추가 2026-08-27 11:30:52 +09:00
chanii8 c02ac6f425 Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-26 17:12:36 +09:00
mind-one001 2fe3631a0d Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-26 16:26:19 +09:00
mind-one001 e0cdba00be 그리드 정렬기능 2026-08-26 16:25:29 +09:00
kiminchan 5e206dd210 Merge branch 'iipbs/sn/dev' into iipbs/sn/dev_kic 2026-08-26 14:44:57 +09:00
kiminchan 2008a5a9ee 통수위험 지도 안내 및 과거이력 조회 동작 보완 2026-08-26 14:43:27 +09:00
chanii8 8bda55c653 [as] feat: 행정지원 신청 패널 전체접기/펼침 기능 2026-08-26 14:04:36 +09:00
mind-one005 53aeeccc2a Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-26 13:11:52 +09:00
mind-one005 c7738a6d6c sn: 침수예측현황 지도이동시 도시침수지도 layer점검 2026-08-26 13:11:40 +09:00
kiminchan baf29df950 통수위험 매핑 시설 지도 표출 및 과거이력 행 식별 보완 2026-08-26 13:07:01 +09:00
mind-one005 0a13f34092 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-26 11:11:30 +09:00
mind-one005 0b45361135 sn: 침수예측현황 팝업에서 지도이동 2026-08-26 11:11:18 +09:00
chanii8 cff7b7da30 [as] feat: 행정지원 패널 전체접기/펼침 기능 2026-08-26 11:04:37 +09:00
kiminchan 83420c76b8 Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/dev_kic 2026-08-26 10:38:51 +09:00
mind-one005 42f83bcd24 sn: 침수예측현황 목록 항목 순서 변경 2026-08-26 10:06:55 +09:00
chanii8 197c2583ed Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-08-25 17:06:40 +09:00
chanii8 10f5d42122 Merge branch 'iipbs/as/dev' 2026-08-25 17:06:19 +09:00
chanii8 816b6b2ca8 [as] chore: 재원협의 폼 페이지 내 쿼리 무효화 코드 수정 2026-08-25 16:58:31 +09:00
mind-one001 ccfbe6ae32 팝업 병합 그리드 기능 추가 2026-08-25 16:43:07 +09:00
rsone001 64ed53cf4d [ST] refactor: input - 자동 계산 cell 부동소수점 문제 해결을 위해 decimal.js 라이브러리 추가 및 계산 로직 수정, 6-2 지표 로직 수정 2026-08-25 16:04:31 +09:00
mind-one005 c8e9fc54ed Merge branch 'iipbs/wr/dev' 2026-08-25 15:48:05 +09:00
chanii8 6927f77f86 Merge branch 'iipbs/as/dev' 2026-08-25 15:44:20 +09:00
chanii8 9c86058fe1 [as] chore: 설계자문 삭제 시 목록 쿼리 무효화 2026-08-25 15:43:47 +09:00
mind-one001 8126fb844a Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-25 15:30:01 +09:00
mind-one001 23c9ddf4ce ui 버튼 동작 통일 2026-08-25 15:29:55 +09:00
chanii8 c4291ceda5 [as] refactor: 행정지원 쿼리키 관리 방식 변경 2026-08-25 15:29:27 +09:00
chanii8 8dfd7cc290 [as] feat: 설치인가 저장 시 목록/상세 쿼리 무효화 2026-08-25 15:01:19 +09:00
chanii8 e5498ef04b Merge branch 'iipbs/as/dev' 2026-08-25 10:33:14 +09:00
chanii8 7baebf2cbc [as] feat: 부동소수점 처리 2026-08-25 10:31:40 +09:00
chanii8 a4c14433cd [as] feat: 행정지원 문서 파일 첨부 로직 수정 2026-08-25 09:42:05 +09:00
mind-one005 63388ccc90 dm: env 수정 2026-08-24 17:45:53 +09:00
rsone001 043fd3d4a8 [ST] - 입력 6-1 공공하수처리시설 컬럼 설정 변경 2026-08-24 17:19:33 +09:00
LGS bda3228d0f 물 재이용 소개 / 물 재이용 현황 [이미지 수정 및 텍스트 수정] 2026-08-24 16:57:15 +09:00
jcchoi eda0b6b813 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-08-24 16:51:28 +09:00
rsone001 6dd09971f1 [ST] - 컬럼 자동 계산식 오류 수정 2026-08-24 16:39:24 +09:00
LGS 95ca40913f 물 재이용 소개[우리나라의 수자원 현황 및 물이용 여건] 이미지 교체 2026-08-24 16:16:41 +09:00
chanii8 23424b5eee [st] chore: 통계 검증 리포트 초기 조회 파라미터 수정 2026-08-24 14:09:12 +09:00
kiminchan 8bfe1c9ab2 강우관측소 지도 클릭 비활성화 2026-08-24 11:23:40 +09:00
chanii8 4bd5276fc5 [as] rename: 스펙 문서 폴더 이동 2026-08-24 09:32:46 +09:00
chanii8 92ed3233af [as] design: 대시보드 스타일 수정 2026-08-24 09:21:30 +09:00
chanii8 2c1e4d18fc Merge branch 'iipbs/st/dev' 2026-08-21 17:28:11 +09:00
chanii8 3f190dfff6 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-21 17:27:37 +09:00
chanii8 6cc02da7a4 [st] feat: 통계 검증 리포트 목록 조회 API 수정 2026-08-21 17:27:30 +09:00
kiminchan 0dc981819f 강우 레이어 프록시와 관측소 아이콘 추가 2026-08-21 17:22:25 +09:00
rsone001 c87ccf8788 [ST] - fix: 입력 저장&제출시 빈값 null처리 2026-08-21 14:56:34 +09:00
chanii8 dcaa2d5ef3 Merge branch 'iipbs/as/dev' 2026-08-21 11:42:18 +09:00
chanii8 e0715ce177 [as] feat: 증감액, 증감률 서버 데이터 매핑(사업규모, 총사업비, 재원분담) 2026-08-21 11:38:43 +09:00
chanii8 3333f1c8e6 [as] style: 행정지원 문서 진행 상태 테이블 스타일 수정 2026-08-21 09:59:14 +09:00
mind-one001 4b7ada9362 Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/dev
# Conflicts:
#	apps/sn/app/(map-layout)/map/components/waterRisk/WaterRiskPopupPage.tsx
#	apps/sn/src/features/windowPopup/model/popupDefinitions.ts
2026-08-20 17:16:33 +09:00
mind-one001 71c5dfb8dd fix(sn): 조사관리 팝업 레이아웃과 등록 흐름 개선 2026-08-20 17:14:07 +09:00
mind-one001 7e3911dbd6 feat(sn): 침수예측 및 통수위험 팝업 UI 보완 2026-08-20 17:13:54 +09:00
kiminchan 839d31b9ca 통수위험 결과조회 공통 그리드 및 지도 표출 보완 2026-08-20 17:09:33 +09:00
rsone001 8e757ab9db Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-20 16:31:13 +09:00
rsone001 de7e66afb4 [ST] - fix: 11-4 데이터 체크 수정 2026-08-20 16:31:00 +09:00
chanii8 63050b0efa [as] design: 대시보드 전국 행정지원 지도 레이아웃 구현 2026-08-20 15:13:18 +09:00
mind-one001 386fdade59 Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/dev 2026-08-20 15:10:36 +09:00
mind-one001 0cbbc3b934 feat(sn): CS 기준 UI 통일 및 지도 팝업 표준화 2026-08-20 14:57:20 +09:00
chanii8 c4592a026a Merge branch 'iipbs/st/dev' 2026-08-20 13:08:24 +09:00
chanii8 c981b83284 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-20 13:07:50 +09:00
chanii8 927bb7cb3a [st] chore: eslint 해결 2026-08-20 11:48:18 +09:00
chanii8 725976b5c0 [st] feat: 시도/시군구 조회 쿼리 변경 2026-08-20 11:47:50 +09:00
rsone001 cdbe6a855f [ST] - fix: 검토 하수관로 컬럼 수정 및 엑셀 내보내기 셀 스타일 수정 2026-08-19 12:31:37 +09:00
chanii8 a1c96e9e98 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-19 11:58:06 +09:00
chanii8 1488805242 [as] feat: 통계 검증 리포트 새로고침 버튼 추가 2026-08-19 11:57:58 +09:00
chanii8 cf681125a6 [as] docs: 문서 수정 2026-08-19 11:40:13 +09:00
chanii8 7dcef46829 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-18 18:02:05 +09:00
chanii8 36a7654973 Merge branch 'iipbs/as/dev' 2026-08-18 18:01:37 +09:00
rsone001 5b12f81b8f [ST] - fix: 통계 집계표 조회 기본값 수정, 엑셀 다운로드 스타일 적용, 요약표 마우스 툴팁 추가 / 입력단 오류 수정 및 검토단 컬럼 설정 수정 2026-08-18 17:44:22 +09:00
chanii8 7c88b609f7 [as] feat: 대시보드 공지사항 섹션 구현 2026-08-18 17:35:54 +09:00
kiminchan f5051225ea 통수위험 결과조회 및 지도 시각화 프로토타입 구현 2026-08-18 17:28:48 +09:00
chanii8 5af4f0f27b [as] feat: 대시보드 페이지 구현 2026-08-18 16:40:21 +09:00
mind-one005 f7218fc98a sn: 침수예측현황-단위 엑셀다운로드 수정 2026-08-18 13:31:22 +09:00
chanii8 cf115b769f Merge branch 'iipbs/as/dev' 2026-08-13 17:50:18 +09:00
chanii8 774144846d [as] chore: 하수도기본계획 게이트 코드 수정 2026-08-13 17:49:57 +09:00
chanii8 3b2f6c386d Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-13 17:21:09 +09:00
chanii8 987e5fcbfd [as] chore: 알림이력 notiUrl 리졸버 추가 2026-08-13 17:20:30 +09:00
chanii8 335872eaec [as] feat: 행정지원 문서 상세페이지 게이트 구현 2026-08-13 17:11:13 +09:00
rsone001 df24e7c2a8 [ST] - fix: 일반 검증 수식 안내 문구 수정 2026-08-13 16:20:54 +09:00
rsone001 3b5fee047b [ST] - fix: qa 조치, 일반 검증 개발 수정 2026-08-13 15:30:00 +09:00
chanii8 9bcc8b95e7 [as] feat: 알림이력 페이지 구현 2026-08-13 11:51:21 +09:00
mind-one001 c9e3ff0d91 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-12 11:14:05 +09:00
mind-one001 474ad1ab26 GIS관리 기능 보완 2026-08-12 11:13:49 +09:00
jcchoi 9ea3474d43 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-11 18:00:02 +09:00
jcchoi a26bd5ea5b 이미지 정리 2026-08-11 17:59:52 +09:00
chanii8 4ee0eab653 Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-11 17:45:56 +09:00
chanii8 fe92ba3c27 [as] feat: 내행정지원 페이지 2026-08-11 17:45:28 +09:00
mind-one005 099e000f88 sn: 분석도구 - 침수예측현황 2026-08-11 15:09:30 +09:00
mind-one005 7fa4538667 sn: 도시침수예방사업 다운로드 header 수정 2026-08-11 14:34:47 +09:00
mind-one005 91e11a7857 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-11 14:33:20 +09:00
chanii8 e8870d3bcb [as] chore: 불필요한 상수 제거 2026-08-11 11:43:18 +09:00
mind-one001 d582293e5f 보수이력 목록 조회 항목 추가 2026-08-11 11:28:52 +09:00
jcchoi 9152782a65 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-11 11:14:20 +09:00
jcchoi 0dc378285c 전화번호 수정 2026-08-11 11:14:08 +09:00
mind-one001 d8c5344451 관망도 분석도구 위치 조정 2026-08-11 11:11:09 +09:00
mind-one001 d23a16905d Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-11 10:32:45 +09:00
mind-one001 2649ce286b 보수이력 기능 개발 2026-08-11 10:32:35 +09:00
mind-one005 2f9e97b65b sn: 도시침수예방사업01 2026-08-10 22:04:36 +09:00
chanii8 403946c50a Merge branch 'iipbs/st/dev' 2026-08-10 18:01:40 +09:00
chanii8 bdc7c64c61 Merge branch 'iipbs/as/dev' 2026-08-10 18:01:33 +09:00
rsone001 181730bcde [ST] - fix: 입력: 새로고침 시 열려있는 모든 텝의 공통 api가 실행되는 현상 수정, 하수도보급률 저장&제출 request 수정(외국인총인구 제거, 총면적 추가), 검증 툴팁 max-width 추가, 지난 검증 결과 및 제출 결과 데이터 마우스 툴팁 추가 2026-08-10 17:58:05 +09:00
chanii8 d79e0e585a [as] feat: 행정지원 업무별 상세페이지 내 진행상태 섹션 추가 2026-08-10 17:57:39 +09:00
chanii8 678af2f06e [st] chore: 승인 정보 입력 폼 수정 2026-08-10 15:48:19 +09:00
chanii8 dbda77a75b [as] feat: 결재현황 ui 수정 2026-08-10 15:27:53 +09:00
chanii8 0459c0b231 [as] feat: 행정지원 업무별 목록 내 진행상황 ui 수정 2026-08-10 10:26:27 +09:00
mind-one005 e1940043e9 sn: 노후 하수관로 정비사업 등록/수정 입력란 수정 2026-08-07 22:49:44 +09:00
mind-one005 7f1c804a0c dm: LoginModal 사업자등록번호 입력 수정 2026-08-07 20:59:05 +09:00
mind-one005 d07bb6074d sn: 노후하수관로정비사업 시군구 필수 아님 2026-08-07 18:22:08 +09:00
rsone001 82d67bb4c6 [ST] - fix: 검증 항목 명칭 변경 2026-08-07 17:22:08 +09:00
chanii8 ed54fe9646 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-07 17:02:06 +09:00
rsone001 85ef1805e5 [ST] - 공단 오류 및 개선 요청사항 처리 2026-08-07 16:24:39 +09:00
mind-one005 4109691eea Merge branch 'iipbs/sn/dev_ljp' into iipbs/sn/dev 2026-08-07 15:58:28 +09:00
mind-one005 4eaac5dbf1 sn: 노후하수관로정비사업 - 팝업 2026-08-07 15:57:14 +09:00
chanii8 07a625e03b [as] rename: SectionCollapsible 컴포넌트 shared 레이어로 이동 2026-08-07 15:34:26 +09:00
mind-one001 b99fe8daf1 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-08-07 15:28:49 +09:00
mind-one001 3f4e3581dd 준설관리 개발완료 2026-08-07 15:28:33 +09:00
mind-one005 f00627270f sn: 최종확정예산사업 popup 2026-08-06 18:40:19 +09:00
chanii8 ee19f9ca59 Merge branch 'iipbs/cm/dev' 2026-08-06 17:11:52 +09:00
chanii8 b9e9814664 Merge branch 'iipbs/st/dev' 2026-08-06 17:11:50 +09:00
chanii8 8303fa3860 Merge branch 'iipbs/as/dev' 2026-08-06 17:11:38 +09:00
chanii8 6d9042ac0f [cm] typo: 404 페이지 문구 수정 2026-08-06 17:06:58 +09:00
chanii8 26f64988ef [st] typo: 404 페이지 문구 수정 2026-08-06 17:06:00 +09:00
chanii8 12a0a37d24 [as] typo: 404 페이지 문구 수정 2026-08-06 17:04:56 +09:00
chanii8 c8c11b19de Merge branch 'iipbs/as/dev' 2026-08-06 16:49:45 +09:00
chanii8 45a500817f [as] chore: 검토인력관리 인원배치 api 수정 2026-08-06 16:39:36 +09:00
chanii8 8a352a8e69 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-08-06 16:18:00 +09:00
chanii8 999fd610af [as] feat: 결재선 검증 로직 수정 2026-08-06 16:17:44 +09:00
chanii8 dd6aff5ebf Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-08-06 15:52:02 +09:00
chanii8 bc3c841b89 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-08-06 15:51:58 +09:00
chanii8 99ea453d23 Merge remote-tracking branch 'origin/iipbs/cm/dev' 2026-08-06 15:51:46 +09:00
chanii8 71389a104c Merge remote-tracking branch 'origin/iipbs/cs/dev' 2026-08-06 15:51:34 +09:00
chanii8 6e2ebe9025 [as] feat: 결재선관리 > 결재구분 변경 시 결재자 초기화 2026-08-06 15:39:33 +09:00
chanii8 29e39f98af Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-06 15:15:09 +09:00
chanii8 5192f3e3c0 [st] feat: 404 페이지 구현 2026-08-06 15:15:04 +09:00
chanii8 95c8416754 [cm] feat: 404 페이지 구현 2026-08-06 15:14:46 +09:00
chanii8 b102c4feba [as] feat: 404 페이지 구현 2026-08-06 15:14:26 +09:00
chanii8 daff1610e3 [cs] feat: 404 페이지 구현 2026-08-06 15:12:48 +09:00
chanii8 054dcc4eee [common/components] chore: 모듈 참조 경로 수정 2026-08-06 13:40:02 +09:00
chanii8 1f1f72451b [common/components] feat: 페이지 타이틀 내 도움말 다이얼로그 구현 2026-08-06 13:37:31 +09:00
mind-one005 1858739bb9 sn: 노후하수관로정비사업 조회조건 유지 2026-08-06 10:46:47 +09:00
kiminchan 3d320d0e19 맨홀 선택 시 기존 심볼이 유지되도록 보완 2026-08-06 09:42:41 +09:00
kiminchan 9d2c0afb98 조사 지도 조건과 노후관로 범례 보완 2026-08-06 09:14:56 +09:00
chanii8 ae51b8273f Merge branch 'iipbs/as/dev' 2026-08-05 17:22:50 +09:00
chanii8 defe2a8e9b [as] fix: 타입 에러 2026-08-05 17:22:28 +09:00
chanii8 28aa932676 Merge branch 'iipbs/as/dev' 2026-08-05 17:20:23 +09:00
chanii8 4aa2d0a62c [as] chore: 행정지원 메뉴 수정 2026-08-05 17:18:38 +09:00
mind-one001 29f2a9169e Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev
# Conflicts:
#	apps/sn/src/features/windowPopup/model/popupDefinitions.ts
#	apps/sn/src/features/windowPopup/registry.tsx
2026-08-05 16:24:52 +09:00
mind-one001 493acc9ebd 통수능/준설관리 2026-08-05 16:05:07 +09:00
chanii8 1d8d9c7b3d [as] feat: 행정지원 메뉴(설치인가, 재원협의, 설계자문, 하수도기본계획) 수정 2026-08-05 15:58:48 +09:00
mind-one005 d3bdc58994 sn: 순번 삭제 외 2026-08-05 15:49:55 +09:00
kiminchan 20efccb4d9 Merge branch 'iipbs/sn/dev_kic' into iipbs/sn/dev 2026-08-05 13:58:16 +09:00
kiminchan f58daf3bac 침수지도 목록보기 및 WMS 표출 구현 2026-08-05 13:55:22 +09:00
rsone001 83de317308 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-08-05 10:09:58 +09:00
rsone001 84b1fd07ae [ST] - management service code refresh 로직 추가 2026-08-05 10:08:26 +09:00
mind-one005 b42810d29a sn: 노후하수관로정비사업1 2026-08-04 20:30:26 +09:00
chanii8 e105f52fea [as] feat: 기본계획 상세 페이지 구현 2026-08-04 17:22:21 +09:00
mind-one005 2e6a75141b sn: 노후 하수관로 정비사업 2026-08-03 18:16:42 +09:00
chanii8 f637f096f9 chore: x-device-id 제거 코드 수정 (domain 옵션 추가) 2026-08-03 17:45:10 +09:00
chanii8 7f497f60af Merge branch 'iipbs/st/dev' 2026-08-03 17:24:35 +09:00
chanii8 7a019b063b Merge branch 'iipbs/cs/dev' 2026-08-03 17:24:30 +09:00
chanii8 e5e20a789a Merge branch 'iipbs/as/dev' 2026-08-03 17:24:24 +09:00
chanii8 8883f5ba55 Merge branch 'iipbs/cm/dev' 2026-08-03 17:24:18 +09:00
chanii8 d64a694e20 docs: authService 리팩토링 문서 업데이트 2026-08-03 17:24:02 +09:00
chanii8 a88f8ab33e [st] feat: 로그아웃 라우트 핸들러 적용 2026-08-03 15:56:48 +09:00
chanii8 5e3d263750 [cs] feat: 로그아웃 라우트 핸들러 적용 2026-08-03 15:40:48 +09:00
chanii8 70136aa65d [as] feat: 로그아웃 라우트 핸들러 적용 2026-08-03 14:57:47 +09:00
chanii8 b514bdc472 [cm] feat: 로그아웃 라우트 핸들러 적용 2026-08-03 14:53:42 +09:00
chanii8 5031f0c582 Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-08-03 11:36:57 +09:00
chanii8 25287dc3d9 [as] chore: 기본계획 스키마 수정 2026-08-03 11:36:45 +09:00
chanii8 5527ad8c41 Merge branch 'iipbs/st/dev' 2026-08-03 11:20:13 +09:00
chanii8 75525ec7a8 Merge branch 'iipbs/cs/dev' 2026-08-03 11:20:04 +09:00
chanii8 21e967473b Merge branch 'iipbs/as/dev' 2026-08-03 11:19:50 +09:00
chanii8 c67b5844b6 [st] chore: metadata 수정 2026-08-03 11:14:48 +09:00
chanii8 ced62ec0c4 [cs] chore: metadata 수정 2026-08-03 11:12:06 +09:00
chanii8 84bbaa09da [as] chore: metadata 수정 2026-08-03 11:08:42 +09:00
chanii8 dfa35542c5 [cm] chore: metadata 수정 2026-08-03 11:06:43 +09:00
chanii8 a6348050fd [as] refactor: 로그인 페이지 auth 그룹으로 분리 2026-08-03 11:03:03 +09:00
chanii8 4d700c3566 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-08-03 10:55:33 +09:00
chanii8 f2d7e947d7 [cm] refactor: 로그인 페이지 auth 그룹으로 분리 2026-08-03 10:52:01 +09:00
chanii8 3c65e6f8f5 chore: css 모듈 처리 2026-08-03 10:43:17 +09:00
kiminchan ce60e5f5fb Merge branch 'iipbs/sn/dev' into iipbs/sn/dev_kic 2026-08-03 10:12:47 +09:00
kiminchan c9d85a8ce4 조사결과 관로·맨홀 지도 레이어와 보고서 조회 연동 2026-08-03 10:11:48 +09:00
chanii8 1efe2dbe89 remove: ClientIpGuard 제거 2026-07-31 17:56:21 +09:00
chanii8 b2260e0b61 [as] chore: 기본계획 신청 페이지 수정 2026-07-31 17:32:25 +09:00
chanii8 3c2ca24dea [as] feat: 하수도기본계획 > 이전기본계획조회 다이얼로그 구현 2026-07-31 16:58:46 +09:00
chanii8 2acbeb83d0 [as] feat: 행정지원 업무 신청 시 시도/구군 선택 로직 추가 2026-07-31 13:50:07 +09:00
chanii8 e0956e05a6 [as] chore: 설치인가 조회 조건 수정 2026-07-31 09:55:05 +09:00
chanii8 e67492853c [as] WIP: 기본계획조회 다이얼로그 구현 2026-07-31 09:30:09 +09:00
mind-one001 0f8b6cd6d0 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev
# Conflicts:
#	apps/sn/app/(map-layout)/map/components/gisManagement/GisManagementPopupPage.tsx
#	apps/sn/app/(map-layout)/map/components/waterRisk/WaterRiskPopupPage.tsx
2026-07-30 17:26:41 +09:00
mind-one001 b11658e1d6 준설관리 초안 2026-07-30 17:18:59 +09:00
kiminchan bcf5d3caec 조사관리 팝업 UI와 관로 상세 등급 표시 보정 2026-07-30 16:53:30 +09:00
kiminchan 474676af17 조사 상세현황 배수방식·관종 명칭 표시 2026-07-30 13:17:16 +09:00
chanii8 36e56dcaef Merge branch 'iipbs/st/dev' 2026-07-30 11:28:08 +09:00
chanii8 e42e28d062 [st] chore: 통계검증리포트 > 승인정보입력 다이얼로그 수정 2026-07-30 11:27:36 +09:00
chanii8 0786053022 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-07-30 11:24:13 +09:00
chanii8 e56938f7ff [as] WIP: 하수도기본계획 신청 페이지(이전기본계획조회) 2026-07-30 11:23:44 +09:00
rsone001 791ec1ae2d [ST] - code-refresh 로직 추가 2026-07-30 11:01:02 +09:00
kiminchan 3cc0e9de14 조사 상세조회 및 집계 엑셀 다운로드 연동 2026-07-30 10:53:32 +09:00
chanii8 b8b0b24b0e [as] feat: dc service 추가 2026-07-29 15:39:37 +09:00
kiminchan ff7be74437 Merge branch 'iipbs/sn/dev' into iipbs/sn/dev_kic 2026-07-29 13:31:01 +09:00
kiminchan f52f22e80f 조사결과 검토·저장 및 사업보고서 목록 표시 개선 2026-07-29 13:22:59 +09:00
mind-one005 86e00d48dc sn: 지도 침수흔적도 우측 정보에 강우분석 추가 2026-07-29 11:43:51 +09:00
chanii8 afbebb9dfc [as] feat: 하수도기본계획 목록 페이지 구현 2026-07-29 10:24:38 +09:00
mind-one005 bf61f6f76a sn: 침수발생이력 팝업 수정 2026-07-28 17:51:47 +09:00
kiminchan 62c41297c4 조사사업 수정 및 조사양식 다운로드 연동 2026-07-28 13:28:15 +09:00
rsone001 4e4942313a [ST] - fix: 통계일정관리 불필요 컬럼 제거 2026-07-28 11:34:46 +09:00
mind-one005 09b5cc3798 sn: 시도별 노후관로현황 2026-07-27 17:51:33 +09:00
chanii8 fe76864a05 [as] feat: 하수도기본계획 등록 폼 레이아웃 구현 2026-07-27 17:29:23 +09:00
kiminchan 155a727a77 처리분구 인라인 편집 및 저장 후 목록 동기화 2026-07-27 14:18:02 +09:00
kiminchan a52dacf500 조사사업 등록의 사업차수 입력과 행정구역 배치 보완 2026-07-27 11:29:48 +09:00
mind-one005 e8e38310ee Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-07-27 10:32:25 +09:00
mind-one005 2652e2cfe0 sn: components.css 통합 2026-07-27 10:29:45 +09:00
kiminchan 5a76ae8a88 Merge branch 'iipbs/sn/dev' into iipbs/sn/dev_kic 2026-07-27 10:20:27 +09:00
kiminchan 7d9b97aac9 조사상세 본단위구간 모드 및 결함별 엑셀 다운로드 구현 2026-07-27 10:15:06 +09:00
mind-one005 69c3183e35 sn: 침수흔적도 지도표출 2026-07-24 18:13:54 +09:00
chanii8 e6fcbfbde4 chore: tsserver 용량 관리 2026-07-24 16:25:55 +09:00
chanii8 ed4d1b73cb refactor: X-Device-Id 공통 로직으로 변경 2026-07-24 15:53:23 +09:00
mind-one005 5e6477cddd Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-07-24 15:23:41 +09:00
mind-one005 c13cab394a sn: 침수발생이력 변수명 수정 2026-07-24 15:23:35 +09:00
kiminchan 73f1940cb0 Merge branch 'iipbs/sn/dev' into iipbs/sn/dev_kic 2026-07-24 15:12:08 +09:00
mind-one005 8a35d232ef sn: 침수발생이력 팝업 api 적용 2026-07-24 14:22:41 +09:00
kiminchan 728981cfe6 조사 결함 분류별 상세보기 및 등록 팝업 개선 2026-07-24 14:08:25 +09:00
kiminchan 11cb2b9044 조사사업 관로·맨홀 상세보기 팝업 구현 2026-07-24 13:05:26 +09:00
chanii8 e3dac52d7e Merge branch 'iipbs/st/dev' 2026-07-24 10:56:09 +09:00
chanii8 d1d8a1eff3 [st] feat: 통계 검증 리포트 시도/시군구 셀렉트 로직 수정 2026-07-24 10:47:57 +09:00
chanii8 cae9f94388 [as] docs: 설계자문 구현 문서 생성 2026-07-24 09:57:40 +09:00
jcchoi 4502d55ce1 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-07-23 20:49:31 +09:00
chanii8 cad87496ac [as] feat: 기본계획 엔티티 생성 2026-07-23 17:41:16 +09:00
kiminchan c1d6cd7b8e 조사사업 보고서 다운로드 및 상세현황 그리드 보정 2026-07-23 17:26:08 +09:00
kiminchan 477426c384 조사사업 등록·결과 업로드 및 관로·맨홀 종합집계 화면 구현 2026-07-23 17:06:01 +09:00
rsone001 3d3b978070 [st] - fix: 검토이력조회 request 누락 수정 2026-07-23 16:36:29 +09:00
rsone001 117082c862 [st] - fix: 입력 메뉴 데이터 항목 추가 시 해당사항없음 false로 갱신되지 않던 오류 수정 2026-07-23 14:26:11 +09:00
chanii8 bfa03aabc0 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-23 11:43:30 +09:00
chanii8 f535cd1010 [st] feat: 대시보드 일정안내 empty 구현 및 일정관리 월 이동 구현 2026-07-23 11:33:21 +09:00
chanii8 b388edb23c [as] feat: 업무신청에서 재이용수 선택 로직 구현 2026-07-22 17:03:46 +09:00
chanii8 2f6d4ebec1 chore: 코드 정리 2026-07-22 14:13:38 +09:00
rsone001 1c3cc5b61b Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-22 13:39:57 +09:00
rsone001 db7584cb01 [st] - fix: 집계06.공공하수처리시설 조회 조건 추가, 입력06.공공하수처리시설 매핑 계약기간 DatePicker 수정, 입력자 예외 제출 valuedation 수정 2026-07-22 13:38:58 +09:00
kiminchan ec60a05b01 처리분구관리 화면 구현 2026-07-22 13:15:06 +09:00
chanii8 b842408f41 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-07-22 11:36:12 +09:00
chanii8 6e33a62ed9 [as] feat: 설계자문 페이지 구현 2026-07-22 11:34:38 +09:00
mind-one005 36231a5eca Merge branch 'iipbs/wr/dev' 2026-07-22 09:23:43 +09:00
mind-one005 90f0e4691b Merge branch 'iipbs/wr/dev' into iipbs/sn/dev 2026-07-21 21:03:19 +09:00
mind-one005 b41a317788 wr: fo: sn: 지도화면 session confirm 2026-07-21 17:59:15 +09:00
chanii8 84fb81b245 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-07-21 15:56:50 +09:00
chanii8 e18f759fab [as] feat: 설계자문 엔티티 생성 2026-07-21 14:32:14 +09:00
chanii8 ce9c7b17a2 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-07-21 13:54:32 +09:00
chanii8 c2d6f7fe37 Merge remote-tracking branch 'origin/iipbs/cs/dev' 2026-07-21 13:54:28 +09:00
chanii8 9b18aa9cef Merge remote-tracking branch 'origin/iipbs/cm/dev' 2026-07-21 13:54:22 +09:00
chanii8 5eccb58aa9 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-07-21 13:54:16 +09:00
chanii8 acce1364fa Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-21 13:53:30 +09:00
chanii8 625c5f5fe2 [st] chore: 토큰 자동 리프레시 코드 주석 처리 2026-07-21 13:53:24 +09:00
chanii8 dffa9d74a0 [cs] chore: 토큰 자동 리프레시 코드 주석 처리 2026-07-21 13:52:59 +09:00
chanii8 668a8ef844 [cm] chore: 토큰 자동 리프레시 코드 주석 처리 2026-07-21 13:52:26 +09:00
chanii8 df4d708981 [as] chore: 토큰 자동 리프레시 코드 주석 처리 2026-07-21 13:51:10 +09:00
chanii8 c48a46c7ab feat: 토큰 시간 만료 시 authService.logout 함수 실행 2026-07-21 13:50:19 +09:00
chanii8 da844fb3a7 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-21 11:50:36 +09:00
chanii8 3e1f89d90f [common/components] chore: 로그인 연장 콘솔 추가(임시) 2026-07-21 11:47:39 +09:00
mind-one005 559e4ee352 Merge remote-tracking branch 'origin/main' into iipbs/wr/dev 2026-07-21 11:38:33 +09:00
jcchoi a1d43dc65c 차수 단어 제외 2026-07-21 11:11:55 +09:00
chanii8 d26cd90b02 [common/components] feat: nofooter에 authService props 추가 2026-07-21 09:20:37 +09:00
chanii8 45f72f7333 [common/components] chore: auth-header 참조 경로 수정 2026-07-20 18:42:15 +09:00
chanii8 196d31a084 [common/components] feat: raw fetch 헤더에 토큰 주입 2026-07-20 18:35:14 +09:00
chanii8 aaf898fb4c Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/cm/dev 2026-07-20 17:47:19 +09:00
rsone001 533e89c919 [st] - fix: 이상치선별보고서 css 수정 2026-07-20 17:11:10 +09:00
chanii8 2f0448a98e [common/components] refactor: banner, menu, site를 기본 fetch 로 수정 2026-07-20 17:08:36 +09:00
chanii8 aab8cf9622 [cm] feat: 인증 서비스에 "X-Device-Id" 적용 2026-07-20 15:37:17 +09:00
kiminchan 5e0693e057 Merge branch 'iipbs/sn/dev_kic' into iipbs/sn/dev 2026-07-20 15:33:27 +09:00
kiminchan 1708555133 시설물 레이어 체크 시 스크롤 위치 유지 2026-07-20 15:29:23 +09:00
mind-one005 c38d95fd60 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-07-20 15:03:52 +09:00
kiminchan 667f7b2094 Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/dev_kic 2026-07-20 14:57:39 +09:00
kiminchan d17909df41 그리드 공통 정렬 기능 확대 적용 2026-07-20 14:56:23 +09:00
mind-one005 88113fca1c wr: fo: sn: NEXT_PUBLIC_COOKIE_COMAIN 개발 추가 2026-07-20 14:52:48 +09:00
chanii8 81fc947c9d chore: as, st 앱 standalone 활성 2026-07-20 13:49:22 +09:00
chanii8 c684440e65 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-20 13:45:21 +09:00
chanii8 4c6222acf1 Merge branch 'iipbs/cs/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-20 13:45:12 +09:00
chanii8 2131ecc315 Merge branch 'iipbs/cm/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-20 13:45:07 +09:00
chanii8 f53c72f31f Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-07-20 13:44:16 +09:00
chanii8 1d3910c157 [st] chore: route handler 쿠키 도메인 설정 구문 수정 2026-07-20 13:43:46 +09:00
chanii8 3b2f5c2d5d [cm] chore: route handler 쿠키 도메인 설정 구문 수정 2026-07-20 13:42:55 +09:00
chanii8 903cdaad45 [as] chore: route handler 쿠키 도메인 설정 구문 수정 2026-07-20 13:41:15 +09:00
chanii8 386fc61a3f [cs] chore: route handler 쿠키 도메인 설정 구문 수정 2026-07-20 13:39:43 +09:00
dongsilove 6e354690c5 sn: NEXT_PUBLIC 제거 2026-07-19 23:52:05 +09:00
dongsilove adae670318 Merge branch 'iipbs/wr/dev' into iipbs/sn/dev 2026-07-19 23:03:36 +09:00
dongsilove bdbefbf600 :wr :fo :dm NEXT_PUBLIC 삭제 2026-07-19 20:40:41 +09:00
dongsilove 939089a79c :wr :sn :fo NEXT_PUBLIC_COOKIE_DOMAIN 추가 2026-07-19 18:14:34 +09:00
dongsilove f8354344ff Merge branch 'iipbs/wr/dev' 2026-07-18 14:02:49 +09:00
dongsilove e596c1f0a6 wr: sn: fo: X-Device-Id 추가 2026-07-18 14:01:33 +09:00
chanii8 7f53badcf3 chore: auth service 수정 2026-07-16 18:33:35 +09:00
chanii8 1dcf01bb0e chore: 의존성 업데이트 2026-07-16 18:26:58 +09:00
chanii8 eec25ab1c4 Merge branch 'iipbs/st/dev' 2026-07-16 18:26:24 +09:00
chanii8 f90f093385 Merge branch 'iipbs/cs/dev' 2026-07-16 18:26:13 +09:00
chanii8 befe56fd44 Merge branch 'iipbs/cm/dev' 2026-07-16 18:26:02 +09:00
chanii8 47d91424c5 Merge branch 'iipbs/as/dev' 2026-07-16 18:25:49 +09:00
chanii8 ddd72f71d2 [st] feat: 로그인 시 X-Device-Id 헤더 추가 2026-07-16 18:24:39 +09:00
chanii8 1a1ff2ec30 [cs] feat: 로그인 시 X-Device-Id 헤더 추가 2026-07-16 18:22:58 +09:00
chanii8 10d89279bc [cm] refactor: auth service 수정 2026-07-16 18:21:37 +09:00
chanii8 a6f9ba7526 [as] feat: 재원협의 보완요청 로직 작업 2026-07-16 17:54:57 +09:00
mind-one005 19dfe45b0b wr: sn: fo: BaseLayout 호출 param 추가 2026-07-16 16:58:26 +09:00
chanii8 f584e21480 [as] feat: 설치인가 보완요청 및 완료 기능 구현 2026-07-16 15:17:43 +09:00
dongsilove 6acacbfcf4 sn: 공통 모듈 기반 로그인 변경 2026-07-15 22:04:26 +09:00
mind-one005 52a79eca60 fo: 공통 모듈 기반 로그인 변경 2026-07-15 18:22:39 +09:00
chanii8 70faadb27e [as] feat: 보완요청 기능 구현 2026-07-15 17:52:43 +09:00
mind-one005 2eb1809f36 wr: 공통 모듈기반 로그인 변경 2026-07-15 17:22:51 +09:00
kiminchan d9febf8010 하수관로 편집 세션 UI 및 토글 동작 개선 2026-07-15 15:54:15 +09:00
kiminchan d4c5787360 하수관로 편집 세션 배치 및 팝업 전환 개선 2026-07-15 15:39:38 +09:00
mind-one005 c8c461e8e8 Merge remote-tracking branch 'origin/main' into iipbs/wr/dev 2026-07-15 14:54:42 +09:00
chanii8 9e2da003f1 [as] feat: 전결 로직 수정 및 결재현황 섹션 구현 2026-07-15 13:36:20 +09:00
rsone001 ae41483bb7 [ST] - fix: 집계 컬럼 설정 fixedColumns 수정 2026-07-15 11:39:09 +09:00
kiminchan d062b8a7d4 지반침하 연계 관로 탭의 부모 종속 제거 처리 2026-07-15 11:34:06 +09:00
rsone001 18825dd0c3 [ST] - DataGrid setRow 로직 수정 및 간단한 수정사항 처리 2026-07-15 11:29:49 +09:00
mind-one005 6b2ea58b66 wr: Password 초기화 2026-07-15 10:26:56 +09:00
kiminchan fdfb37d4d0 지반침하 지도 UX 및 목록 표시 개선 2026-07-14 16:57:51 +09:00
mind-one005 450de39f8c dm: 로그인 변경 2026-07-14 16:48:08 +09:00
chanii8 c87ad8ea1b [as] refactor: 결재선 선택 다이얼로그 통합 2026-07-14 15:03:45 +09:00
mind-one005 b336651503 dm: 등록시 비번 해쉬처리 2026-07-14 14:50:54 +09:00
chanii8 7a1949fc6f [as] feat: 결재선 수정 사항 반영 2026-07-14 12:28:22 +09:00
rsone001 35a63d7c5c [st] - fix: 지난년도 데이터 가져오기 button confirm 추가 2026-07-14 11:54:29 +09:00
kiminchan 031f5e2ed8 하수관망도 시도 단위 처리시설 조회 지원 2026-07-14 10:18:46 +09:00
kiminchan 189b4b7599 지반침하 연계 탭 유형 구분·관로 단일화·부모 복귀 기능 및 후속 정책 반영 2026-07-14 09:55:06 +09:00
dongsilove 6ada32cb9f Merge branch 'iipbs/wr/dev' 2026-07-13 18:24:54 +09:00
chanii8 5491c524c5 [as] feat: 멀티 디바이스 로그인 구현 2026-07-13 18:08:26 +09:00
chanii8 8d29f25101 [as] feat: 체크리스트 전체선택 기능 추가 2026-07-13 16:43:01 +09:00
kiminchan 28d4b2d831 RDII 분석 진행률 표시 및 결과 팝업 가독성 개선 2026-07-13 15:20:04 +09:00
chanii8 399645a80a [as] feat: 설계자 전화번호 검증 코드 추가 2026-07-13 14:43:55 +09:00
chanii8 9d13809d01 [as] feat: 체크리스트 항목 관리에 '정렬 순서' 컬럼 추가 2026-07-13 14:28:10 +09:00
chanii8 e8c8db7383 [as] chore: 불필요한 코드 제거 2026-07-13 14:05:25 +09:00
chanii8 b41814ed0c [as] chore: 체크리스트 정렬 코드 제거 (서버에 위임) 2026-07-13 12:01:17 +09:00
chanii8 d71f2043de [as] feat: 필수 첨부 파일 업로드 가능한 확장자 제한 2026-07-13 11:38:38 +09:00
kiminchan 87ba3e8fa2 침입수 분석 전역 프로그래스바 표시와 상태 polling 연동 마감 2026-07-13 11:26:15 +09:00
chanii8 eabbbf456f Merge branch 'iipbs/cm/dev' 2026-07-13 11:10:46 +09:00
chanii8 ebfba7ac8c [cm] chore: 프로젝트 설정 수정 2026-07-13 11:07:32 +09:00
mind-one005 fe4a4a420e wr: EMFILE 오류수정 2026-07-13 10:51:16 +09:00
rsone001 14ae432833 [st] - fix: 입력단 superAdmin 필드 존재 시에만 추가되도록 수정 2026-07-10 12:27:52 +09:00
kiminchan 009a38f3ca RDII 분석 완료 상태 polling 및 전역 완료 alert 적용 2026-07-09 18:51:01 +09:00
chanii8 cfc73e4094 Merge branch 'iipbs/as/dev' 2026-07-09 17:53:18 +09:00
chanii8 748be6faa5 [as] chore: 파비콘 파일 추가 2026-07-09 17:51:20 +09:00
chanii8 330e2e5c69 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-07-09 17:39:35 +09:00
chanii8 343a3f81bf [as] feat: 설치인가 전자결재 로직 수정 2026-07-09 17:39:03 +09:00
mind-one005 13c7d5a0c5 wr: 중수도시설 등록 페이징 수정 2026-07-09 17:38:57 +09:00
kiminchan a9c38b356a 침입수 즉시 제출 alert 복구 및 전역 완료 alert 감시 추가 2026-07-09 17:30:21 +09:00
kiminchan 4181257afd 하수관망도 검색바 한글 깨짐 복구 2026-07-09 16:23:21 +09:00
chanii8 0141ed56c2 [as] feat: 재원협의 전자결재 로직 수정 2026-07-09 16:17:51 +09:00
kiminchan 35dc205810 Merge branch 'iipbs/sn/dev_kic' into iipbs/sn/dev 2026-07-09 16:12:21 +09:00
mind-one005 d10a17eaf6 sn: 테마변경 1차 2026-07-09 15:57:30 +09:00
mind-one005 a623c53fa5 wr: css ㅍ 2026-07-09 15:39:48 +09:00
kiminchan 3db8babee7 침입수·RDII 분석 대상지역 기본값을 양평으로 설정 2026-07-09 13:03:21 +09:00
kiminchan ca7ac992e2 침입수 분석결과 실측하수량 관로 우선 줌인 및 지역 선이동 제거 2026-07-09 11:37:26 +09:00
rsone001 a956c9894e [st] - 공지사항 메뉴 새창으로 열도록 return 수정 2026-07-09 11:35:50 +09:00
kiminchan 6c84ba6aeb 침입수·RDII 분석 화면 결과보기 버튼 및 지도결과 popup 연계 2026-07-09 10:45:53 +09:00
mind-one005 e99b7d8162 Merge branch 'iipbs/wr/dev' 2026-07-09 10:01:38 +09:00
mind-one005 09bb613929 wr: 오류수정 2026-07-09 10:00:20 +09:00
mind-one005 761b0dd502 wr: 수요처팝업 비번 초기화 2026-07-09 09:56:41 +09:00
mind-one005 8c72cb3b72 wr: 하수재이용실적 등록 2026-07-08 20:56:51 +09:00
chanii8 35b6f4ae54 [as] feat: 결재선 저장 api 수정 2026-07-08 19:22:40 +09:00
chanii8 cf5ade639a Merge remote-tracking branch 'origin/iipbs/cm/dev' 2026-07-08 18:52:53 +09:00
chanii8 0dfd5efa97 [cm] feat: auth 서비스 수정 및 jwt 기반 로그인 만료 구현 2026-07-08 18:52:30 +09:00
chanii8 e6cbb9dfd8 Merge branch 'iipbs/st/dev' 2026-07-08 18:21:36 +09:00
chanii8 52c7215cd8 Merge branch 'iipbs/cs/dev' 2026-07-08 18:21:28 +09:00
chanii8 fe88732a80 Merge branch 'iipbs/as/dev' 2026-07-08 18:21:22 +09:00
chanii8 aa5f85a3c1 [st] remove: 로그아웃 라우트 핸들러 제거 2026-07-08 18:21:00 +09:00
chanii8 843519c3b5 [as] remove: 로그아웃 라우터 핸들러 제거 2026-07-08 18:17:03 +09:00
chanii8 c74917d1d9 [cs] remove: 로그아웃 라우터 핸들러 제거 2026-07-08 18:16:24 +09:00
chanii8 9dca199d78 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-08 18:05:51 +09:00
chanii8 4fa7542aca [st] feat: 통계 검증 리포트 반려 처리 로직 수정 2026-07-08 18:05:35 +09:00
chanii8 1cd24b5dd5 [st] chore: stClient 참조 경로 수정 2026-07-08 18:05:11 +09:00
rsone001 34f1bf5e6a Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-08 17:45:01 +09:00
rsone001 675e5f58d1 [st] - fix log detail 로직 수정 2026-07-08 17:39:35 +09:00
mind-one005 127fb41145 dm: 수요신청현황 api 수정, 수정시 비번 처리,무한루프처리 2026-07-08 17:05:57 +09:00
chanii8 8abf32bb53 Merge branch 'iipbs/as/dev' 2026-07-08 16:04:15 +09:00
chanii8 b567e5df13 [as] remove: instl-sncn-old 페이지 제거 2026-07-08 16:03:16 +09:00
chanii8 40acdafed2 Merge remote-tracking branch 'origin/iipbs/cs/dev' 2026-07-08 16:00:49 +09:00
chanii8 7f6b05e4e5 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-07-08 16:00:38 +09:00
chanii8 0d4a698c11 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-08 15:57:24 +09:00
chanii8 5bacfb04b5 [st] chore: auth 서비스 수정 2026-07-08 15:57:17 +09:00
chanii8 1e8514a0bc Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-08 15:56:41 +09:00
chanii8 b6714dd8a4 [as] chore: auth 서비스 수정 2026-07-08 15:55:54 +09:00
chanii8 e05c2a03e0 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-07-08 15:53:10 +09:00
chanii8 914df505ae [cs] chore: auth 서비스 수정 2026-07-08 15:52:14 +09:00
chanii8 7e59ae6b66 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-08 15:46:08 +09:00
chanii8 eb84a23d7e [common] feat: useSessionExtendConfirm 생성 2026-07-08 15:43:42 +09:00
mind-one005 1076120516 dm: 전화번호 포맷 & 비번 변경 2026-07-08 10:32:33 +09:00
dongsilove 54ce26adbc Merge remote-tracking branch 'origin/iipbs/wr/dev' 2026-07-07 20:30:15 +09:00
chanii8 28d54b615b [cs] chore: auth service 수정 2026-07-07 18:19:43 +09:00
chanii8 424fa576fb [cs] feat: auth service 수정 (BaseLayout DI 패턴) 2026-07-07 18:04:08 +09:00
chanii8 7f8e5fe9f9 [as] feat: auth service 수정 (BaseLayout DI 패턴) 2026-07-07 18:02:18 +09:00
chanii8 47f1bd012c Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-07-07 17:51:28 +09:00
chanii8 0c2e762cee [cm] chore: next-env.d.ts 파일 깃에서 제거 2026-07-07 17:49:43 +09:00
chanii8 68124e6abb [common/components] chore: BaseLayout 수정 2026-07-07 17:37:26 +09:00
chanii8 5587e6b6a3 [common] feat: 공통 auth 및 components 수정(authService DI 패턴) 2026-07-07 17:34:27 +09:00
rsone001 ac1e98fbaa Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-07 17:02:47 +09:00
rsone001 d25f9f3a01 [st] - fix: 2-3 컬럼 로직 수정, 입력단 excel 업로드 시 행정동코드 매핑 검사 2026-07-07 17:01:36 +09:00
mind-one005 fb638752eb wr: 검색어 있는 경우 paging과 건축물용도 2026-07-07 16:42:51 +09:00
chanii8 8c3dd62f6d Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-07-07 15:14:27 +09:00
chanii8 e79635b81a [st] feat: 통계 검증 리포트 페이지 수정 2026-07-07 15:14:18 +09:00
rsone001 e3a9851de6 [st] - fix: 하수관로 관종별 현황, 입력 가능 연도만 매설연장 컬럼 수정가능, 관리자 계정일 시 엑셀 업로드로 모든 cell 수정 가능 2026-07-07 12:17:44 +09:00
mind-one005 34811d4571 Merge branch 'main' into iipbs/sn/dev 2026-07-07 11:03:47 +09:00
chanii8 3167a9a6e3 WIP: 전자결재 로직 수정 2026-07-07 09:18:50 +09:00
mind-one005 a29d1f0cc2 wr: 수요처 pk변경 1 2026-07-06 17:37:28 +09:00
jcchoi e76cddd45b Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-07-06 16:53:09 +09:00
rsone001 3c214f4725 [st] - fix: chat.js registerd error 2026-07-06 14:54:32 +09:00
rsone001 80861938f3 [st] - fix: 입력단 텝 형식 그리드 제출 및 저장 후 재조회 로직 수정 2026-07-06 14:11:25 +09:00
rsone001 401f2dba13 [ST] - SearchFrom 수정 2026-07-06 11:29:56 +09:00
jcchoi c4d7c516ef Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-07-05 10:58:48 +09:00
chanii8 2f11b1185b [as] feat: 재원협의 신청 및 수정 페이지 작업 2026-07-03 17:23:18 +09:00
rsone001 42274a71b7 Merge remote-tracking branch 'origin/feature/sein' into iipbs/st/dev 2026-07-03 16:19:48 +09:00
rsone001 f1fcfa9ee4 [st] - 그리드 style 적용 로직 수정, 로그 조회 일정 선택 로직 수정(최대 1년 제한) 2026-07-03 16:19:27 +09:00
yangsein afcfbbbf61 feat: 집계표 비고 제거 , 행정동 인구관리 갱신 버튼추가 2026-07-03 16:15:34 +09:00
mind-one005 defba2962d wr: 하수재이용시설 등록 수요처 추가 2026-07-03 15:17:10 +09:00
rsone001 4c7666169c Merge remote-tracking branch 'origin/feature/sein' into iipbs/st/dev 2026-07-03 13:09:41 +09:00
yangsein 5580b4301f [feat]집계 11분뇨찌꺼기 tab2 소화조 컬럼 매핑 수정 2026-07-03 13:02:40 +09:00
jcchoi 421ff8cb40 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-07-02 18:31:43 +09:00
mind-one005 96b95e0eb9 Merge branch 'iipbs/wr/dev' 2026-07-02 18:08:03 +09:00
rsone001 cea2d39df8 [st] - 일정등록, 입력단 지난검증결과 알림 뱃지, 로그 상세조회, 검토단 cell style 적용 2026-07-02 17:46:13 +09:00
kiminchan f4eae0d33e 지반침하 좌표 실패 이슈 및 잔여 케이스 문서 반영 2026-07-02 15:38:45 +09:00
chanii8 716a20cc22 [as] feat: 코드관리 페이지 통합 2026-07-02 15:35:56 +09:00
mind-one005 873a74492a wr: refresh token 적용 2026-07-02 15:33:06 +09:00
kiminchan 516d93d6fa RDII 분석결과 팝업 지도연계 및 결과전용 팝업 적용 2026-07-02 11:38:40 +09:00
mind-one005 958a8ffe2c Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-07-02 09:10:49 +09:00
mind-one005 0286e2a92e Merge branch 'iipbs/wr/dev' 2026-07-02 09:10:14 +09:00
mind-one005 01e339cc61 wr: 하수재이용시설 조회 api 정리2 2026-07-01 18:18:36 +09:00
chanii8 0e7c9b4a96 [as] feat: 재원분담 API 추가 2026-07-01 17:52:57 +09:00
kiminchan d5814043d9 침입수 분석결과 지도연계 및 I/I 분석 메뉴 정리 2026-07-01 17:35:58 +09:00
chanii8 e81210ec04 [as] feat: 재원협의 신청 페이지 작업 및 설치인가 신청 폼 섹션 공통 컴포넌트화 2026-07-01 17:19:48 +09:00
mind-one005 8d17623d3a wr: 하수재이용시설 조회 api 정리1 2026-07-01 15:18:42 +09:00
jcchoi ba9d7c8172 Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m9s
2026-07-01 10:13:30 +09:00
mind-one005 06f5a3cde0 wr: 하수재이용실적 등록1 2026-06-30 18:14:19 +09:00
rsone001 25417dcda1 [st] - any type 수정, 검토 그리드 cell style 수정 2026-06-30 17:59:11 +09:00
jcchoi 9a3b332130 Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m57s
2026-06-30 16:02:29 +09:00
rsone001 1236a24b61 [st] - fix: 입력단 날짜 데이터 값 없을 시 null 처리 2026-06-30 15:56:47 +09:00
rsone001 f50de743e3 [st] - feat 통계인구관리 기능 추가 2026-06-30 14:56:52 +09:00
rsone001 0c2568aad0 Merge remote-tracking branch 'origin/feature/sein' into iipbs/st/dev 2026-06-30 14:40:51 +09:00
yangsein b15cbeb568 feat: 행정동인구 탭 추가 2026-06-30 14:36:25 +09:00
rsone001 afdc06638f [st] - 검토단 작업 완료 및 입력6-1, 10-1 운영데이터업데이트 기능 추가, 텝 영역 우클릭 후 텝 닫기 기능 추가 2026-06-30 14:23:08 +09:00
chanii8 6fb610d9aa [as] feat: fnrs-cnsltn-mng 서비스 구현 2026-06-30 13:51:22 +09:00
kiminchan c14d536133 정보검색 기본계획보고서 탭 조회 UX 개선 2026-06-30 13:09:07 +09:00
chanii8 3008ae2eeb Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 3m41s
2026-06-30 12:27:39 +09:00
chanii8 3b4d9df0e8 [as] feat: 설치인가 수정 페이지 수정 2026-06-30 12:27:13 +09:00
mind-one005 48d4a5e135 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-06-30 09:05:23 +09:00
mind-one005 ff33565513 wr: 지도 overlay 버튼 수정 2026-06-29 18:18:48 +09:00
chanii8 b40bae1b50 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 5m53s
2026-06-29 18:00:17 +09:00
chanii8 0533d229e6 [as] feat: 설치인가 검토의견 작성 폼 추가 2026-06-29 17:59:18 +09:00
mind-one005 bbb755bf56 wr: 오류 수정 2026-06-29 17:46:17 +09:00
kiminchan 31caee87db GIS 편집 임시 관로 스냅 지원 및 세션 취소/뒤로가기 분리 2026-06-29 17:45:31 +09:00
kiminchan 87cda451f3 지반침하 설정 패널 연도선택 UI 및 우측 탭 스타일 정리 2026-06-29 17:08:31 +09:00
mind-one005 e2b8f901a3 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-06-29 16:54:03 +09:00
mind-one005 951e2c1f89 wr: 지도 팝업 제목 style 통일 2026-06-29 16:53:43 +09:00
chanii8 20fd291cc3 [as] feat: 설치인가 신청 시 업무구분 선택 팝업 추가 2026-06-29 16:08:21 +09:00
kiminchan ee3ddce41d 지반침하 상세 관로 용도 OGC011 매핑 및 표 너비 조정 2026-06-29 14:51:46 +09:00
chanii8 fb160e712b [as] chore: 설치인가 페이지 수정 2026-06-29 14:46:40 +09:00
mind-one005 9a6cc1779f wr: 지도 목록보기 다운로드할 데이터 ... 오류 수정 2026-06-29 14:39:52 +09:00
chanii8 b459d0fbcb Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 8m47s
2026-06-29 13:50:05 +09:00
chanii8 9816a9c46a [as] feat: 첨부파일 다운로드 기능 추가 2026-06-29 13:49:33 +09:00
kiminchan f4b8f9d65d 우측 상세 패널 탭 지도 선택 동기화 2026-06-29 13:31:31 +09:00
mind-one005 08e8d7343c Merge commit '5505791b6e3ef04480731b123512e90e9e035c43' 2026-06-29 12:56:12 +09:00
mind-one005 5505791b6e wr: 하수재이용등록 화면 속도개선-오류수정 2026-06-29 11:48:37 +09:00
mind-one005 1862477641 wr: 하수재이용등록 화면 속도개선 2026-06-26 17:34:54 +09:00
kiminchan ff9daa2bfd Merge remote-tracking branch 'origin/iipbs/wr/dev' into iipbs/sn/dev_kic 2026-06-26 17:30:06 +09:00
kiminchan 23b13e3fdc 하수처리시설 레이어 기준 검색 동기화 2026-06-26 17:29:16 +09:00
mind-one005 f93f2674df sn: 지도 component 수정 반영 2026-06-26 17:23:03 +09:00
chanii8 1d7d34b7c1 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 2m16s
2026-06-26 16:12:39 +09:00
chanii8 9837a7af71 [as] refactor: 버튼 로직 중복 코드 제거 2026-06-26 16:12:18 +09:00
chanii8 d3ef4094c6 [as] feat: 설치인가 문서 삭제 버튼 기능 2026-06-26 15:57:22 +09:00
chanii8 bf86291016 [as] feat: 설치인가 버튼 로직 작업 2026-06-26 15:48:07 +09:00
kiminchan 37ace8b34c Merge remote-tracking branch 'origin/iipbs/wr/dev' into iipbs/sn/dev_kic 2026-06-26 15:46:35 +09:00
kiminchan e170790e4c 지반침하 레이어 설정 UI 및 포인트 표시 개선 2026-06-26 15:40:21 +09:00
mind-one005 c0888ea2a4 wr: 빗물이용시설 엑셀다운 수정 2026-06-26 11:30:57 +09:00
kiminchan 0fe1aa983c 지반침하 반경 내 관로정보 요약 표시 개선 2026-06-26 10:35:40 +09:00
kiminchan 180eb1f59d 관로 이동 도구 on/off 유지 및 입력정보 조회 오류 방어 2026-06-26 10:15:24 +09:00
mind-one005 1ead18b53c wr: 지도팝업 작업계획 2026-06-26 10:01:10 +09:00
kiminchan 9ca61dd60d 지반침하 반경 내 관로정보 경과년도 집계 표시 2026-06-26 09:36:00 +09:00
chanii8 52a0635810 Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 2m53s
2026-06-26 09:15:38 +09:00
chanii8 2337b4cce2 Merge branch 'iipbs/as/dev' 2026-06-26 09:14:22 +09:00
chanii8 6f51b90178 [as] WIP: 설치인가 버튼 로직
[as] WIP: 설치인가 버튼 로직

[as] WIP: 설치인가 버튼 로직
2026-06-26 09:11:32 +09:00
mind-one005 07db5b1fbd wr: 페이징 수정 2026-06-25 18:09:04 +09:00
rsone001 162e18142c [st] - 입력단 작업 완료 2026-06-25 18:02:31 +09:00
mind-one005 6110abebb4 wr: 페이징 오류 작업계획 2026-06-25 16:30:29 +09:00
mind-one005 05e9082831 wr: black 테마 수정 2026-06-25 15:23:04 +09:00
mind-one005 76a634cf9a fo: 테마적용1 2026-06-25 14:34:55 +09:00
kiminchan d4797617a6 정보검색 팝업 초기 코드 로딩 및 최초 조회 보완 2026-06-25 10:40:28 +09:00
kiminchan be67395f13 정보검색 팝업 조회조건 공통코드 초기화 2026-06-25 10:22:40 +09:00
mind-one005 793c673414 wr: 페이지 내 색상 정의 수정2 2026-06-24 18:16:22 +09:00
chanii8 20e17861e1 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 4m4s
2026-06-24 17:59:35 +09:00
chanii8 deb7375d6b [as] WIP: 설치인가 버튼 로직 2026-06-24 17:59:01 +09:00
mind-one005 2a76c279eb wr: 페이지 내 색상 정의 수정1 2026-06-24 16:38:53 +09:00
kiminchan 2767260505 지반침하 레이어 아이콘 PNG 재인코딩 2026-06-24 16:13:52 +09:00
kiminchan bba4a8eeba 시설물 레이어 패널 토글 복구 2026-06-24 15:39:06 +09:00
mind-one005 5d1ba3fd87 wr: theme 적용1 2026-06-24 15:13:16 +09:00
kiminchan c9786adc77 Merge remote-tracking branch 'origin/main' into iipbs/sn/dev_kic 2026-06-24 14:53:40 +09:00
jcchoi 407051287d Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-06-24 09:59:53 +09:00
jcchoi 3d178dd6bc 사용자 검색 수정 2026-06-24 09:59:44 +09:00
chanii8 8ddedc60ac Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 1m56s
2026-06-24 09:09:33 +09:00
chanii8 ddb8031e55 [as] WIP: 설치인가 페이지 2026-06-24 09:07:47 +09:00
mind-one005 99db9a0b77 wr: business_component css 수정1 2026-06-23 22:05:19 +09:00
mind-one005 4126bc2234 wr: 작업계획 2026-06-23 13:32:25 +09:00
mind-one005 d4f1ec095e Merge branch 'iipbs/wr/dev' into iipbs/dm/dev 2026-06-23 11:42:50 +09:00
LEEKYUHYUN bbf3ce3948 wr: 테이블 헤더 복구 2026-06-23 10:38:05 +09:00
rsone001 46cf24c193 [st] - 입력단 기능 작업중, 집계 요약표 다운로드 2026-06-23 10:20:20 +09:00
LEEKYUHYUN fb5b8356c7 물 재이용 소개 이미지 교체 2026-06-22 18:09:16 +09:00
LEEKYUHYUN 1333bd040a 물 재이용 소개 이미지 교체 2026-06-22 18:02:57 +09:00
LEEKYUHYUN 013c07ef59 Merge branch 'iipbs/dm/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/dm/dev 2026-06-22 17:55:20 +09:00
mind-one005 29fd59a321 wr: 수요처 readonly 처리 2026-06-22 17:42:10 +09:00
mind-one005 3bc152c341 dm: 용어수정 2026-06-22 16:35:19 +09:00
LEEKYUHYUN ea274f8f7d 랜딩 페이지 이미지 교체 2026-06-22 16:33:16 +09:00
LGS 7ac53e56d7 WR dashboard-black theme edit-Leeinseo 2026-06-22 13:17:12 +09:00
LEEKYUHYUN 7b1c12e952 블루 테마 카드 스타일 수정_FO 2026-06-22 13:10:50 +09:00
LGS 17dafe6530 Merge branch 'iipbs/dm/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/dm/dev 2026-06-22 13:09:57 +09:00
LGS ef331363f7 Revert "[ wr ] 물재이용 매칭시스템 Black 테마 css 추가"
This reverts commit 4f10864c2c.
2026-06-22 13:09:53 +09:00
LGS 4f10864c2c [ wr ] 물재이용 매칭시스템 Black 테마 css 추가 2026-06-22 13:09:20 +09:00
Leeinseo 4b5ceb0e0a FO dashboard-red theme edit-Leeinseo 2026-06-22 13:06:30 +09:00
mind-one005 4fa50161fd wr: width 조정 2026-06-22 09:43:09 +09:00
mind-one005 3d27e07ccb fo: theme css 추가 2026-06-19 17:27:30 +09:00
mind-one005 ca1ca71a62 Merge branch 'iipbs/wr/dev' into iipbs/dm/dev 2026-06-19 16:45:02 +09:00
mind-one005 bdd6c7b804 dm: theme css 추가 2026-06-19 16:44:30 +09:00
mind-one005 04b8082f04 wr: width 고정 2026-06-19 16:15:48 +09:00
yangsein 82ab1376cc feat:행정동 탭코드 추가 2026-06-18 15:38:10 +09:00
kiminchan 2c27a67cc3 위성지도 기능 개선 및 침수 레이어 범례 보완 2026-06-18 11:03:39 +09:00
mind-one005 3451b19b96 wr: next-env.d.ts 2026-06-18 10:59:41 +09:00
mind-one005 ae7c1ea1c4 Merge branch 'iipbs/wr/dev' 2026-06-18 10:47:12 +09:00
mind-one005 9c76b955e2 wr: 오류 수정 2026-06-18 10:35:10 +09:00
mind-one005 6c4bfe2798 wr: 수요처 매칭 검토 2026-06-18 10:27:00 +09:00
mind-one005 98189cd14a dm: 총대장균군수 단위 수정 2026-06-17 17:25:18 +09:00
mind-one005 823ce262bb wr: 수요처 매칭 검토 천단위 , 건수 10건 2026-06-17 16:29:28 +09:00
mind-one005 34358400e8 wr: 지도점검 현황 팝업 table 항목명 수정 2026-06-17 14:58:41 +09:00
mind-one005 da9d069a9a wr: 총대장균군수 단위 수정 외 2026-06-17 13:32:23 +09:00
mind-one005 58a03678e4 wr: 시설명 자동검색 제거 2026-06-16 18:16:14 +09:00
yangsein 5ea68435ed Merge remote-tracking branch 'origin/iipbs/st/dev' into feature/sein 2026-06-16 16:44:28 +09:00
yangsein 58ca85ce90 feat: 통계인구관리 내국인/외국인/행정동갱신 탭 구현" 2026-06-16 16:43:06 +09:00
mind-one005 f5c281cf86 wr: 시군구 해당 위치로 2026-06-16 15:51:44 +09:00
chanii8 c72bcea7d9 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 1m1s
2026-06-16 15:05:15 +09:00
chanii8 49cd160e76 Merge branch 'iipbs/cs/dev'
Deploy Frontend / deploy (push) Successful in 1m2s
2026-06-15 17:43:18 +09:00
chanii8 5ca93a25ca [cs] chore: 상위코드 목록 컬럼 수정 2026-06-15 17:42:55 +09:00
kiminchan 2ea207a63a 통수위험 팝업 디자인 보완 2026-06-15 17:34:24 +09:00
chanii8 55554fc781 Merge branch 'iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m49s
2026-06-15 16:43:55 +09:00
chanii8 8e78c10304 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-06-15 16:43:35 +09:00
chanii8 0eb74b46ba [st] feat: 메뉴 경로에 '/cms' 포함 시 탭 로직 예외 처리 2026-06-15 16:42:38 +09:00
rsone001 f21dde8f7d [st] - tab & menu 로직 수정 2026-06-15 16:34:02 +09:00
kiminchan e47c00f0b6 침수흔적도 목록/지도 상세 팝업 mock 구현 및 md 업데이트 2026-06-15 16:28:37 +09:00
mind-one005 1fc6707bb3 wr: 총대장균군수 단위 수정 2026-06-15 16:27:41 +09:00
rsone001 44c5d66afa Merge remote-tracking branch 'origin/feature/sein' into iipbs/st/dev 2026-06-15 15:54:04 +09:00
mind-one005 04edb49c9f wr: 지도 back 테마 적용 2026-06-15 15:48:05 +09:00
yangsein 7483d351c3 Merge remote-tracking branch 'origin/iipbs/st/dev' into feature/sein 2026-06-15 14:53:01 +09:00
rsone001 e3eb542e59 [st] - 입력단 메뉴 작업중 2026-06-15 10:38:33 +09:00
yangsein 428b199dd3 feat:12하수및분뇨관리사업 2026-06-15 01:02:13 +09:00
yangsein 3c4a6efeea feat:10.물재이용 2026-06-15 01:01:51 +09:00
yangsein dae7024043 feat:[집계표]13.하수도 서비스수준 2026-06-15 01:00:31 +09:00
yangsein 64e5c26e8c feat: [집계표] 12.하수및 분뇨관리사업 2026-06-15 00:59:03 +09:00
yangsein 84ae60edd6 feat:[집계표] 11하수및 분뇨찌꺼기 2026-06-15 00:57:39 +09:00
yangsein d4c9989069 feat: [집계표] 물재이용 2026-06-15 00:56:20 +09:00
yangsein 6cda413913 feat:[집계표]09.하수도 재정 2026-06-15 00:55:06 +09:00
yangsein a11eaf031d feat:[집계표]08.개인하수처리시설 2026-06-15 00:52:54 +09:00
yangsein 3c701359fd feat:[집계표]분뇨처리시설 2026-06-15 00:50:54 +09:00
yangsein 85ba9fe0ab feat:[집계표]05 펌프장 2026-06-15 00:48:45 +09:00
yangsein 71077f4c8e feat:[집계표]04.하수저류시설 및 빗물 펌프장 2026-06-15 00:47:11 +09:00
yangsein 4c7ff53d78 feat:[집계표]03.하수관로 유지관리 2026-06-15 00:43:03 +09:00
changwng bdc3a94ef1 [as frontend] 결제자 지정및 결제 요청 테이블에 넣는 로직 추가 2026-06-12 13:26:41 +09:00
kiminchan 83a3810301 시설물 레이어 ui 변경 작업 2026-06-12 13:26:35 +09:00
mind-one005 b31691dd9c dm: 물재이용 소개, 사례 id 수정 2026-06-12 10:33:30 +09:00
changwng 0647df733a [as frontend] 결제 관련 로직 추가 2026-06-11 19:00:22 +09:00
mind-one005 0131cae0d7 wr: 하수재이용시설등록 후 목록 재조회 2026-06-11 17:45:58 +09:00
chanii8 f37b19eab6 Merge branch 'iipbs/cs/dev'
Deploy Frontend / deploy (push) Successful in 5m3s
2026-06-11 16:38:24 +09:00
chanii8 7c1db5e0e0 [cs] refactor: useSearchParamsState 생성 및 적용 2026-06-11 16:37:15 +09:00
kiminchan c6309bf14d 지반침하정보 총 결과 건수 표시 2026-06-11 11:36:13 +09:00
mind-one005 2c495ab8fd wr: help 이미지 수정 2026-06-11 09:27:25 +09:00
dongsilove ced1b7084d wr: refresh token 원복 2026-06-10 23:22:44 +09:00
kiminchan c10a2ae1c8 지반침하정보 아이콘 표출 오류 수정 2026-06-10 18:18:53 +09:00
kiminchan 3dd09d9fc7 하수관로 상세정보 조회속도 개선 2026-06-10 17:53:48 +09:00
kiminchan 46f05dbfa0 지도 최소 줌레벨 및 경계 개선 2026-06-10 16:12:18 +09:00
mind-one005 b0c871eff7 wr: 하수재이용시설 등록 수정 2026-06-10 15:30:37 +09:00
changwng 4148a87f32 [as frontend] 시도/구군 추가 2026-06-10 14:16:43 +09:00
mind-one005 bd83fa3417 Merge remote-tracking branch 'origin/main' into iipbs/wr/dev 2026-06-10 11:36:11 +09:00
mind-one005 c0b63a655e wr: isReset 삭제 2026-06-10 11:35:09 +09:00
mind-one005 cc21aaede1 wr: 하수재이용시설현황 속도 개선 2026-06-10 11:16:10 +09:00
jcchoi 0c71c80a25 대시보드 알림림 스크롤 처리 2026-06-09 17:29:50 +09:00
kiminchan 823d1d7812 지반침하 연도 조건과 팝업 조회조건 동기화 정리 2026-06-09 16:42:15 +09:00
kiminchan a274b5a261 지반침하 팝업 조회조건, 다운로드, 지도표출 방식 정리 2026-06-09 15:26:09 +09:00
rsone001 49e42757bc Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-06-09 10:34:45 +09:00
chanii8 0964f442be Merge branch 'iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 2m48s
2026-06-09 10:08:45 +09:00
chanii8 73ceeb1b38 [st] feat: 대시보드 공지사항 api 수정 2026-06-09 10:08:00 +09:00
changwng 06f68ec392 Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev 2026-06-08 18:21:40 +09:00
changwng e5dc347276 [as frontend] 시도, 군구 추가 2026-06-08 18:21:35 +09:00
changwng 7c6c43b334 [as frontend] 시도, 군구 추가 2026-06-08 18:06:01 +09:00
kiminchan 8737d1eb0a gis 편집 스냅핑, 꼭지점 제거 기능 구현 2026-06-08 17:40:10 +09:00
chanii8 abf53afafc [as] chore: 검토인력관리 사용자명에 직급 텍스트 추가 2026-06-08 17:04:38 +09:00
chanii8 fc70963fa9 [as] feat: 검토인력 인터페이스 수정 & 결재선관리 페이지 수정 2026-06-08 16:18:42 +09:00
mind-one005 814365d7de wr: common-codes error 메시지 수정 2026-06-08 11:44:12 +09:00
mind-one005 8c1a976b5b wr: isInit 2026-06-08 11:02:11 +09:00
mind-one005 66db81071e wr: 빌드오류 해결 2026-06-08 09:06:58 +09:00
mind-one005 6df6e612bc wr: 공통코드 조회 수정 2026-06-05 17:44:51 +09:00
chanii8 84013993c9 [as] feat: 공종코드 및 회계코드 하위코드 추가 방식 변경
Deploy Frontend / deploy (push) Successful in 9m46s
2026-06-05 17:18:14 +09:00
kiminchan a123588e32 지반침하정보 지도, 상세, 설정, 목록 기능 구현 2026-06-05 16:30:58 +09:00
mind-one005 3fee2a781f wr: 하수재이용시설등록 수정 2026-06-05 16:15:46 +09:00
chanii8 acaf2dd9f4 [as] feat: 검토인력관리 페이지 구현 2026-06-05 14:32:14 +09:00
rsone001 55839f0913 Merge branch 'main' into iipbs/st/dev 2026-06-05 10:40:29 +09:00
rsone001 f68131fc13 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-06-05 10:39:50 +09:00
rsone001 7b9081a125 [st] - feat: 입력 04 작업 중 & 수정 요청 사항 적용 2026-06-05 10:35:31 +09:00
chanii8 0947b76a6e Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-06-05 10:33:30 +09:00
chanii8 23c18da399 next 버전 업그레이드
> [2026 security release](https://vercel.com/changelog/next-js-may-2026-security-release)
2026-06-05 10:29:01 +09:00
chanii8 ec18f17c79 [as] feat: 검토인력관리 서비스 추가 2026-06-05 10:22:25 +09:00
chanii8 cdfa1e56e5 [as] chore: 버튼 색상 변경 2026-06-05 09:34:22 +09:00
chanii8 434c3cfc08 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 6m20s
2026-06-04 17:49:42 +09:00
chanii8 aee3449578 [as] feat: 공종코드관리, 회계코드관리 페이지 구현 2026-06-04 17:49:00 +09:00
mind-one005 54d3a35b38 wr: store code 조회 위치 변경 2026-06-04 15:56:28 +09:00
chanii8 b640fffd0d Merge branch 'iipbs/cs/dev' 2026-06-04 15:56:27 +09:00
chanii8 981e4aab54 Merge branch 'iipbs/as/dev' 2026-06-04 15:56:06 +09:00
chanii8 6b68894d23 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-06-04 15:54:46 +09:00
kiminchan cc91225b01 관로 분석 재질별, 경과년도&재질별 조회 오류 해결 2026-06-04 15:54:32 +09:00
chanii8 97a8b2811e [cs] feat: 회원관리 폼 수정 2026-06-04 15:54:30 +09:00
kiminchan af8db9457f 노후관로 팝업 위치 수정 2026-06-04 15:16:53 +09:00
chanii8 2c3839fc5f Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-06-04 14:43:12 +09:00
changwng e9caa1b84d [as frontend] tech 리뷰 처리 부분 수정 2026-06-04 13:27:09 +09:00
kiminchan 5860cf3a22 Merge remote-tracking branch 'origin/main' into iipbs/sn/dev_kic 2026-06-04 13:08:00 +09:00
chanii8 d2d5502f8d [as] refactor: 코드관리 -> 공종코드관리/회계코드관리 분리 2026-06-04 12:00:47 +09:00
jcchoi 77f2546cc4 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-06-04 11:49:18 +09:00
jcchoi 4df353518f 공통코드(AUTHSECD) 변경에 따른 수정 2026-06-04 11:49:09 +09:00
chanii8 ee114dec7e Merge branch 'iipbs/st/dev' 2026-06-04 11:12:08 +09:00
chanii8 d58b2db786 [st] chore: 검증항목관리 그리드 컬럼 수정 2026-06-04 11:11:36 +09:00
mind-one005 1c2eaef5a4 Merge branch 'iipbs/wr/dev' 2026-06-04 11:06:27 +09:00
changwng bb7cce47ac [as frontend] 체크리스트 뷰시 rvw_stp_com_cd.리뷰단계코드및, 업무구분별로 뷰단에서 보이는 부분정리 2026-06-04 11:02:43 +09:00
mind-one005 ba910d6bfe wr: refresh token관련 수정 2026-06-04 09:54:41 +09:00
changwng 2a8c2ae542 [as frontend] 체크리스트 입력 저장시 validation 및 추가로 서버 저장로직으로 처리 2026-06-02 19:10:56 +09:00
changwng 56f52194a6 [as frontend] 체크리스트 입력 3개* 단계별 입력 *3개 전부 표시되도록 화면 수정 2026-06-02 18:08:48 +09:00
mind-one005 a71b20ba6f wr: refresh token관련 2026-06-02 18:01:48 +09:00
changwng b2282accb1 [as frontend] taskSeComCd 다중체크 처리 로직 구현 2026-06-02 17:01:33 +09:00
mind-one005 5def2ec5ee wr: .env.local 2026-06-02 16:39:40 +09:00
kiminchan 9d6cc405a9 하수관망도 - 처리구역분석 상세정보 매설년도-> 경과년도 변경 2026-06-02 16:29:07 +09:00
kiminchan 1c37591bab 노후관로 경과년도 표시 조건 보완 2026-06-02 15:44:36 +09:00
changwng 9a8350dae8 [as frontend] 체크리스트 조회시 항목 선택 체크 2026-06-02 13:25:06 +09:00
changwng 77fd719f96 [as frontend] 체크리스트 조회시 cklstResultList로 리스트로 처리 하도록 수정 2026-06-02 13:17:09 +09:00
changwng aa831141f6 [as frontend] 체크리스트 항목 뷰에 추가 처리 2026-06-02 11:55:40 +09:00
changwng fb148a77c1 [as frontend] 체크 리스트 표시 여부 적용 2026-06-02 11:30:30 +09:00
changwng d8d98aee6d [as frontend] 공통 수정로직 변경 처리 2026-06-01 18:05:03 +09:00
changwng 87f9b90dbf Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev
# Conflicts:
#	apps/as/src/pages/instl-sncn/ui/cnfm-regist-page.tsx
2026-06-01 17:23:38 +09:00
changwng b704db99b1 [st frontend] 저장시 picRvwCn 검토 내용을 추가 저장 로직 2026-06-01 17:22:32 +09:00
chanii8 f231564548 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 2m23s
2026-06-01 16:54:59 +09:00
chanii8 db04b13310 [as] chore: 체크리스트관리 페이지 수정 2026-06-01 16:50:42 +09:00
chanii8 9958ea72b8 [as] feat: 코드관리 페이지 구현 2026-06-01 16:41:18 +09:00
mind-one005 35f765d870 wr: 하수재이용시설 현황 페이징 수정 2026-06-01 16:31:49 +09:00
chanii8 b6afaba02a [as] feat: 공종코드 & 회계코드 서비스 구현 2026-06-01 16:07:04 +09:00
mind-one001 c7c69ea5a7 gis 편집 2026-06-01 15:49:50 +09:00
changwng 9b9edec627 Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev 2026-06-01 14:18:35 +09:00
changwng d4302550b4 [st frontend] 조회시 체크리스트 3개 부분 조회 처리로 수정함 2026-06-01 14:18:28 +09:00
changwng 6432b9f44a [st frontend] sncnSeComCd 인가 구분 공통 처리 2026-06-01 12:37:06 +09:00
changwng 98f24d8a96 [st frontend] 설치인가 검토단계 코드를 추가함 2026-06-01 11:16:02 +09:00
mind-one005 887549c755 wr: 하수재이용시설 현황 수정 2026-05-29 18:09:59 +09:00
changwng d3ca402e4a [st frontend] 첨부 까지 해서 저장함 2026-05-29 15:57:30 +09:00
chanii8 9ca0e96525 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 4m9s
2026-05-29 14:41:52 +09:00
chanii8 4c28db79df Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-05-29 14:40:48 +09:00
chanii8 6e2a957e99 [as] feat: 필수첨부파일관리 페이지 작업 2026-05-29 14:40:41 +09:00
changwng b5eea1a476 [st frontend] as 수정에 체크리스트 데이터 까지 연결함 2026-05-29 14:39:03 +09:00
changwng 5d31c70e16 [st frontend] as 수정에 폼은 등록폼을 같이 쓰도록 코딩함 2026-05-29 14:31:34 +09:00
changwng a6f05b3850 Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev
# Conflicts:
#	apps/as/src/pages/instl-sncn/ui/cnfm-regist-page.tsx
2026-05-29 13:25:46 +09:00
changwng 49ec6423bd [st frontend] as 사용자 뷰 정보 표시 정리 2026-05-29 13:25:16 +09:00
changwng 68ac1168e9 [st frontend] 상세 페이지 수정 작업 2026-05-29 11:19:49 +09:00
mind-one005 e875df1e55 wr: 하수재이용시설 현황 pageSize설정 2026-05-28 17:48:35 +09:00
chanii8 69bd924cd2 [as] refactor: loginService -> authService 2026-05-28 16:56:15 +09:00
chanii8 164bffc5de Merge branch 'iipbs/st/dev' 2026-05-28 16:10:43 +09:00
chanii8 613f2a36d9 Merge branch 'iipbs/cs/dev' 2026-05-28 16:10:20 +09:00
chanii8 0eee6bf338 [st] refactor: authService 내보내기 방식 변경 2026-05-28 16:09:43 +09:00
mind-one005 c2fdc3aa71 fo: /sites/FO 여러번 호출 수정 2026-05-28 15:31:03 +09:00
changwng e98fe69d68 [st frontend] st frontend 분석 2026-05-28 13:59:53 +09:00
changwng 426e3ffcdf [as frontend] 입력화면 콤포넌트화 처리 2026-05-28 13:59:28 +09:00
chanii8 2e25a0311c [cs] refactor: authService 내보내기 방식 변경 2026-05-28 13:41:53 +09:00
chanii8 97df2c47de Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-05-28 13:38:55 +09:00
chanii8 f7c1cf0718 [as] feat: 필수첨부파일관리 페이지 초안 작업 2026-05-28 13:37:36 +09:00
mind-one005 4d0e39721f Merge branch 'iipbs/wr/dev' 2026-05-28 09:13:23 +09:00
changwng a87f887635 [as instl] 기타 파일 삭제시 실제 첨부 파일에 대해서 삭제여부 Y로 변경 처리 로직 추가 2026-05-27 18:42:47 +09:00
changwng 153f3b7fa7 [as instl] user-store.ts 에 로그인 사용자 필드 추가 및 등록페이지에서 사용 처리 2026-05-27 17:28:37 +09:00
jcchoi 668e750b03 Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 4m23s
2026-05-27 17:20:24 +09:00
rsone001 7e152cacd2 [st] - feat: realgrid 셀 편집 체크 설정 수정, 컴포넌트 url 방식 테스트 중 2026-05-27 17:04:46 +09:00
mind-one005 ac00f978be fo: 빌드오류 수정 2026-05-27 16:52:30 +09:00
mind-one005 c313c1d6cc fo: 지하수 유입량 추정 api 수정 외 2026-05-27 16:15:30 +09:00
chanii8 215d742270 [st] docs: 필수첨부파일관리 페이지 스펙 문서 초안 작성 2026-05-26 17:22:08 +09:00
changwng aa213ceead [as instl] api 에서 as-common-service, as-instl-service, as-checklist-service 의 서비스를 통합관리 as-service로 통합하게 소스 수정함 2026-05-26 16:39:48 +09:00
mind-one005 4eb031ae67 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-05-26 16:05:04 +09:00
mind-one005 b0cd0c772f Merge commit 'e75e38a0b8c00972c158c063ce66664e0b8c2594' 2026-05-26 16:04:47 +09:00
mind-one005 e75e38a0b8 fo: 오수발생량 추정 api 수정 2026-05-26 15:55:50 +09:00
chanii8 86a95ca6e4 Merge branch 'iipbs/st/dev' 2026-05-26 13:19:31 +09:00
chanii8 99fe41b41d [st] feat: H-B 통계검증, I-MR 검증이력 페이지 수정 2026-05-26 13:18:57 +09:00
mind-one005 a78059ec67 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-05-22 16:54:47 +09:00
changwng 50bc502251 [as instl] 기타 파일 그룹 ID 동일하게 업로드 하도록 변경함 2026-05-22 16:02:34 +09:00
mind-one005 7cbde80466 dm: 콘텐츠 관련 refresh 2026-05-22 14:56:58 +09:00
changwng f952f8c262 Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev 2026-05-22 14:36:46 +09:00
changwng 44edac8fbb [as instl] [as 서비스] dongId => admdstCd 로 컬럼 변경 ALTER TABLE sm_bs_as.tb_as_design_cnstn_mng RENAME COLUMN dong_id TO admdst_cd;
ALTER TABLE sm_bs_as.tb_as_fnrs_cnsltn_mng RENAME COLUMN dong_id TO admdst_cd;
                                                ALTER TABLE sm_bs_as.tb_as_instl_sncn_mng RENAME COLUMN dong_id TO admdst_cd;
                                                ALTER TABLE sm_bs_as.tb_as_sewer_bsc_plan RENAME COLUMN dong_id TO admdst_cd; COMMENT ON COLUMN sm_bs_as.tb_as_design_cnstn_mng.admdst_cd IS '행정구역코드';
                                                COMMENT ON COLUMN sm_bs_as.tb_as_fnrs_cnsltn_mng.admdst_cd IS '행정구역코드';
                                                COMMENT ON COLUMN sm_bs_as.tb_as_instl_sncn_mng.admdst_cd IS '행정구역코드';
                                                COMMENT ON COLUMN sm_bs_as.tb_as_sewer_bsc_plan.admdst_cd IS '행정구역코드';
2026-05-22 14:36:41 +09:00
changwng 0e8e646547 [as instl] 설치인가 필수 첨부 파일 수정 2026-05-22 14:11:13 +09:00
chanii8 357dce2161 fix: Cannot find module 에러
Deploy Frontend / deploy (push) Successful in 2m24s
2026-05-22 14:02:48 +09:00
chanii8 430148d843 Merge branch 'iipbs/as/dev' 2026-05-22 13:53:22 +09:00
chanii8 21dab0ef1f [as] feat: 체크리스트관리 및 결재선관리 페이지 수정 2026-05-22 13:52:02 +09:00
changwng da8f66fca1 [as instl] tree를 /list/tree로 url 변경 처리 2026-05-22 13:18:57 +09:00
changwng 31ee840072 [as instl] file upload 시 Middleware 수정 처리 2026-05-22 12:50:23 +09:00
changwng e3d022fcd7 [as instl] 파일 업로드 로직 추가 2026-05-21 18:58:51 +09:00
changwng 09d4750a67 Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev 2026-05-21 18:30:58 +09:00
changwng 8ab63870d4 [as instl] 상태 업데이트 로직 개선: 기존 키의 변화에 따라 상태를 업데이트하도록 수정 Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render. 2026-05-21 18:30:53 +09:00
changwng d3434060ba [as instl service] 신청 저장 로직 추가 2026-05-21 18:25:50 +09:00
chanii8 641132dd56 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 7m56s
2026-05-21 17:39:32 +09:00
chanii8 28bd0cbbf0 Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-05-21 17:39:07 +09:00
chanii8 df4208d4e1 [as] feat: 결재선관리 페이지 작업 2026-05-21 17:38:50 +09:00
changwng ad13875ce8 [as instl service]체크리스트, 공종 리스트 화면 구성 2026-05-21 16:56:12 +09:00
mind-one001 cdaaa34569 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev 2026-05-21 15:57:02 +09:00
mind-one001 17637146be 유하방향 줌레벨 표시 추가 2026-05-21 15:56:33 +09:00
changwng 9673ee0ed2 [as instl] 업무 구분 선택 화면 추가 2026-05-21 14:49:39 +09:00
changwng d864cf73b9 [as instl] 신청 버튼 클릭시 업무구분 팝업 조회로직으로 해서 추가함 2026-05-21 14:33:25 +09:00
kiminchan b4033071a1 하수관로 편집 상세정보 편집 추가, 하수관망도 업로드 시설 선택 보완 2026-05-21 13:57:16 +09:00
changwng 9dec42a584 [as instl] 설치인가 등록페이지 설정 2026-05-21 13:42:50 +09:00
mind-one005 9b4d2d410d Merge branch 'iipbs/dm/dev' 2026-05-21 13:17:39 +09:00
changwng 9e7dea7e9f Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev 2026-05-21 13:13:36 +09:00
changwng 6938c1e96f [as instl service] 회계 코드 연결 작업 2026-05-21 13:13:30 +09:00
mind-one005 4783bbacbd dm: login 입력란 초기화 2026-05-21 11:43:24 +09:00
chanii8 0ef375310f [as] refactor: useConfirm 적용 2026-05-21 11:35:47 +09:00
mind-one005 3eb028b075 Merge branch 'iipbs/wr/dev' 2026-05-20 18:00:42 +09:00
mind-one005 c1a03fcabc wr: 대시보드 표로보기 2026-05-20 17:50:43 +09:00
chanii8 24c446b880 [as] feat: 체크리스트 관리 페이지 생성 2026-05-20 17:33:05 +09:00
mind-one005 48172aa3f0 dm: 사례 콘텐츠 2026-05-20 17:16:56 +09:00
mind-one005 adf5e288d3 Merge branch 'iipbs/dm/dev' 2026-05-20 14:12:09 +09:00
mind-one005 d8d5755849 dm: 소개 html 기록 2026-05-20 14:10:44 +09:00
mind-one005 9f803fd4ad dm: build 에러수정 2026-05-20 09:14:14 +09:00
chanii8 cc4b2ca084 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 2m16s
2026-05-19 17:39:49 +09:00
chanii8 ab3c4e77ee [as] feat: 결재선 추가 로직 수정 2026-05-19 17:39:00 +09:00
chanii8 3c0a16ea67 [as] docs: 체크리스트관리 스펙 문서 초안 생성 2026-05-19 16:31:43 +09:00
chanii8 a6da87837a [as] feat: 체크리스트 엔티티 생성 2026-05-19 16:17:09 +09:00
chanii8 21533c8c0e Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-05-19 16:01:43 +09:00
chanii8 3f75d2751a [as] rename: steiger 적용 2026-05-19 16:01:38 +09:00
chanii8 c5a5b3bde3 [st] feat: 결재선 순서 검증 로직 추가 2026-05-19 14:59:22 +09:00
kiminchan 0c66a8f3b7 통수위험분석 프로토타입 구현 2026-05-19 13:39:25 +09:00
mind-one005 75e619a997 dm: 컨텐츠2 2026-05-19 11:37:07 +09:00
changwng 02f5fae903 Merge branch 'iipbs/as/dev' 2026-05-19 11:03:50 +09:00
changwng 1e3a2fea8d Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev 2026-05-19 11:03:19 +09:00
changwng a4e73284ed [as instl service] vscode 디버그 모듈 활성화 처리및 설치인가 페이지 추가 2026-05-19 11:03:14 +09:00
chanii8 c94d9019d3 [as] refactor: 결재선관리 PK 정책 변경으로 인한 수정 2026-05-19 10:55:23 +09:00
mind-one005 a9d78a7e0d dm: 컨텐츠1 2026-05-19 09:52:18 +09:00
changwng d93157b10a Merge remote-tracking branch 'origin/main' 2026-05-19 09:48:36 +09:00
jcchoi ad18cf1c16 Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m4s
2026-05-18 19:38:04 +09:00
rsone001 90834f3493 [st] - feat: 미입력현황 조회 2026-05-18 19:36:13 +09:00
chanii8 535a6f1ef2 Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 2m40s
2026-05-18 19:15:39 +09:00
chanii8 86566c7692 [as] feat: 결재선관리 페이지 작업 2026-05-18 19:08:38 +09:00
chanii8 51204e2ec1 Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev 2026-05-18 17:40:49 +09:00
chanii8 25affc06b9 [as] feat: 결재선 관리 페이지 구현 2026-05-18 17:40:21 +09:00
jcchoi ad5fc8a2de Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m5s
2026-05-18 17:24:30 +09:00
changwng e2cf4f3f34 Merge remote-tracking branch 'origin/main' 2026-05-18 17:23:08 +09:00
jcchoi 8272093e07 컬럼 수정 2026-05-18 17:22:19 +09:00
rsone001 ba42b74578 Merge remote-tracking branch 'origin/iipbs/st/dev' 2026-05-18 17:21:21 +09:00
jcchoi fba84202f0 검증 수정
Deploy Frontend / deploy (push) Successful in 2m54s
2026-05-18 17:07:09 +09:00
jcchoi f70a20c0c7 검증 수정 2026-05-18 17:04:16 +09:00
rsone001 ac75188980 [st] - fix: DataGrid height 변경 2026-05-18 17:02:12 +09:00
rsone001 404624a583 [st] - fix: 통계이력조회 메뉴 구조 변경 2026-05-18 16:57:18 +09:00
mind-one005 696fdf84d2 wr: 구분에 따른 조회 2026-05-18 16:39:40 +09:00
rsone001 91e505f37f [st] - fix: 권한 및 데이터 상태에 따른 버튼 표출 조건식 오류 수정, 2-6 & 6-2 저장 가능 여부 기능 추가 2026-05-18 16:26:10 +09:00
mind-one005 294fea4f71 Merge remote-tracking branch 'origin/iipbs/dm/dev' into iipbs/wr/dev 2026-05-18 16:18:40 +09:00
jcchoi 148275626c Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev
Deploy Frontend / deploy (push) Successful in 2m58s
2026-05-18 16:16:12 +09:00
jcchoi 7a0a8f23b7 컬럼추가 2026-05-18 16:15:08 +09:00
jcchoi 45bf7b10f3 Merge branch 'iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 2m58s
2026-05-18 16:11:22 +09:00
jcchoi 246f6472a3 검증리포트 항목추가 2026-05-18 16:08:56 +09:00
mind-one005 965b8f3feb wr: 하수재이용시설 현황 조회 수정 2026-05-18 14:47:12 +09:00
jcchoi c37696dee3 Merge branch 'iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m0s
2026-05-18 14:44:10 +09:00
LGS fb61080d8d 물 재이용 소개 - 타법령관의관계검토 이미지 교체 2026-05-18 14:36:26 +09:00
jcchoi d4f46ca344 Merge branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 2m6s
2026-05-18 14:28:02 +09:00
rsone001 50ed22b4bc [st] - fix: 메뉴 권한체크 로직 수정 2026-05-18 12:06:56 +09:00
changwng b0dbd78410 Merge remote-tracking branch 'origin/iipbs/as/dev' 2026-05-18 11:38:15 +09:00
rsone001 b508568e86 [st] - fix: 오류 수정 및 누락 기능 추가 2026-05-18 11:03:43 +09:00
rsone001 8384743a30 Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev 2026-05-18 11:01:20 +09:00
kiminchan ef7bae98d4 하수관망도 업로드 구현 2026-05-18 10:23:33 +09:00
changwng ea9bd64088 Merge remote-tracking branch 'origin/main' 2026-05-18 09:58:23 +09:00
changwng 758d5e7fac [as service] 디버그 모드 활성화및 INSTL_SNCN 설치인가 디렉토리 구성 2026-05-18 09:27:42 +09:00
changwng 658a20f587 feat: package.json 파일에 dev st, as 필터 추가함 2026-05-18 09:26:12 +09:00
mind-one005 9661369708 wr: layout 수정 2026-05-15 18:05:57 +09:00
yangsein 4e8f492006 feat:01.하수도보급률 농어촌 보급률 컬럼 추가
02.하수관로 관종별현황 노후하수관로율 컬럼추가
2026-05-15 16:01:20 +09:00
kiminchan d2a1f5e48b 정보검색, 관로분석(관재질) 팝업 테이블 가로 스크롤 추가 2026-05-15 15:34:25 +09:00
kiminchan 69b717f517 Merge remote-tracking branch 'origin/iipbs/dm/dev' into iipbs/sn/dev_kic 2026-05-15 15:06:46 +09:00
kiminchan 6d0461eaef 하수관로 편집 기능 구현 2026-05-15 15:03:00 +09:00
mind-one005 12e9c2a30c wr: 재이용시설등록 10건 , Uitable 수정 2026-05-15 14:59:33 +09:00
jcchoi 6539e5bb80 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into origin/iipbs/st/dev 2026-05-15 13:04:02 +09:00
jcchoi 91116ccdf7 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
Deploy Frontend / deploy (push) Successful in 4m12s
2026-05-15 11:41:30 +09:00
jcchoi 2bcc49e090 검증결과 리포트 수정정 2026-05-15 11:41:23 +09:00
jcchoi c013759869 그리드 색상 수수정(머지시 , 롤오버시) 2026-05-15 11:41:05 +09:00
mind-one005 bdbba42616 Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend 2026-05-14 17:50:11 +09:00
mind-one005 a84b233435 Merge branch 'iipbs/dm/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/dm/dev 2026-05-14 17:04:27 +09:00
mind-one005 4340a57180 dm: 컨텐츠 조회 2026-05-14 17:04:19 +09:00
mind-one001 be72a594d7 Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev
# Conflicts:
#	apps/sn/app/(map-layout)/map/page.tsx
2026-05-13 17:28:21 +09:00
mind-one001 8cc4f58076 Merge branch 'iipbs/sn/dev2' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev2 2026-05-13 17:17:38 +09:00
mind-one001 3f07739756 유하방향 기능 구현 2026-05-13 16:33:34 +09:00
LGS 1cc6b4b565 메인 컨텐츠 layout 內 세로스크롤 overflow-x-auto 제거 및 헤더 중복 제거 2026-05-13 15:39:27 +09:00
mind-one005 4a6cfed9bb dm: 스크롤 이상 수정 2026-05-13 15:24:56 +09:00
chanii8 0e067bb5fc [as] chore: 로그인 페이지 문구 수정 2026-05-12 16:04:51 +09:00
1904 changed files with 197725 additions and 36940 deletions
+1
View File
@@ -29,4 +29,5 @@ settings.json
.agent
apps/**/tsconfig.tsbuildinfo
packages/**/tsconfig.tsbuildinfo
logs/
+88
View File
@@ -0,0 +1,88 @@
{
// IntelliSense를 사용하여 가능한 특성에 대해 알아보세요.
// 기존 특성에 대한 설명을 보려면 가리킵니다.
// 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요.
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "dev:as 디버그",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"run",
"dev:as"
],
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"sourceMaps": true,
"env": {
"NODE_OPTIONS": "--inspect"
}
},
{
"type": "node",
"request": "launch",
"name": "dev:cs 디버그",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"run",
"dev:cs"
],
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"sourceMaps": true,
"env": {
"NODE_OPTIONS": "--inspect"
}
},
{
"type": "node",
"request": "attach",
"name": "dev:cs 서버 연결 (attach)",
"port": 9229,
"restart": true,
"skipFiles": [
"<node_internals>/**"
],
"sourceMaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/apps/cs/**",
"!**/node_modules/**"
]
},
{
"type": "chrome",
"request": "launch",
"name": "dev:cs 클라이언트 디버그 (Chrome)",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/apps/cs",
"sourceMaps": true
},
{
"type": "node",
"request": "launch",
"name": "dev:st 디버그",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"run",
"dev:st"
],
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"sourceMaps": true,
"env": {
"NODE_OPTIONS": "--inspect"
}
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"chat.tools.terminal.autoApprove": {
"pnpm": true
}
}
+2 -2
View File
@@ -19,7 +19,7 @@
"@tanstack/react-table": "^8.21.2",
"axios": "^1.8.1",
"dayjs": "^1.11.13",
"next": "15.3.8",
"next": "15.5.18",
"react-hook-form": "^7.54.2",
"zod": "^3.24.2"
},
@@ -33,4 +33,4 @@
"tailwindcss": "^3.4.14",
"typescript": "^5.7.2"
}
}
}
+9 -5
View File
@@ -19,11 +19,15 @@
}
],
"paths": {
"@/*": ["./src/*"],
"@common/components": ["../../packages/common/components/dist/"],
"@common/network": ["../../packages/common/network/"]
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"../../packages/common/types/**/*.d.ts"
],
"exclude": ["node_modules", ".next", "dist", "coverage", "storybook-static", "public"]
}
+13
View File
@@ -0,0 +1,13 @@
import "@/app/styles/globals.css";
export const metadata = {
title: "디지털 행정지원 시스템",
};
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<body>{children}</body>
</html>
);
}
@@ -0,0 +1 @@
export { ApprovalLineManagementPage as default } from "@/pages/approval-line-management";
@@ -0,0 +1 @@
export { ChecklistManagementPage as default } from "@/pages/checklist-management";
@@ -0,0 +1 @@
export { CodeManagementPage as default } from "@/pages/code-management";
@@ -0,0 +1,3 @@
import { DashboardPage } from "@/pages/dashboard/ui/dashboard-page";
export default DashboardPage;
@@ -0,0 +1,10 @@
import { DesignEditPage } from "@/pages/design-cnstn";
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <DesignEditPage id={id} />;
}
@@ -0,0 +1,10 @@
import { DesignDetailGate } from "@/pages/design-cnstn";
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <DesignDetailGate id={id} />;
}
@@ -0,0 +1,3 @@
import { DesignListPage } from "@/pages/design-cnstn";
export default DesignListPage;
@@ -0,0 +1,27 @@
import { DesignRegistPage } from "@/pages/design-cnstn";
import { parseTaskSeComCdFromApi } from "@/pages/design-cnstn/lib/design-cnstn-regist-schema";
interface PageProps {
searchParams: Promise<{
taskSeComCd?: string;
admdstCd?: string;
sido?: string;
gugun?: string;
}>;
}
export default async function Page({ searchParams }: PageProps) {
const params = await searchParams;
const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd);
return (
<DesignRegistPage
defaults={{
taskSeComCd: taskSeComCd.length ? taskSeComCd : undefined,
admdstCd: params.admdstCd,
sido: params.sido,
gugun: params.gugun,
}}
/>
);
}
@@ -0,0 +1 @@
export { EsntlAtchFileMngPage as default } from "@/pages/esntl-atch-file-mng";
@@ -0,0 +1,10 @@
import { FnrsEditPage } from "@/pages/fnrs-cnsltn";
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <FnrsEditPage id={id} />;
}
@@ -0,0 +1,10 @@
import { FnrsDetailGate } from "@/pages/fnrs-cnsltn";
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <FnrsDetailGate id={id} />;
}
@@ -0,0 +1,3 @@
import { FnrsListPage } from "@/pages/fnrs-cnsltn";
export default FnrsListPage;
@@ -0,0 +1,27 @@
import { FnrsRegistPage } from "@/pages/fnrs-cnsltn";
import { parseTaskSeComCdFromApi } from "@/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema";
interface PageProps {
searchParams: Promise<{
taskSeComCd?: string;
admdstCd?: string;
sido?: string;
gugun?: string;
}>;
}
export default async function Page({ searchParams }: PageProps) {
const params = await searchParams;
const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd);
return (
<FnrsRegistPage
defaults={{
taskSeComCd: taskSeComCd.length ? taskSeComCd : undefined,
admdstCd: params.admdstCd,
sido: params.sido,
gugun: params.gugun,
}}
/>
);
}
@@ -0,0 +1,10 @@
import { InstlEditPage } from "@/pages/instl-sncn";
interface InstlSncnEditRouteProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: InstlSncnEditRouteProps) {
const { id } = await params;
return <InstlEditPage id={id} />;
}
@@ -0,0 +1,6 @@
import { InstlSncnDetailGate } from "@/pages/instl-sncn";
export default async function InstlSncnDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <InstlSncnDetailGate id={id} />;
}
@@ -0,0 +1,3 @@
import { InstlListPage } from "@/pages/instl-sncn";
export default InstlListPage;
@@ -0,0 +1,28 @@
import { InstlRegistPage } from "@/pages/instl-sncn";
interface InstlSncnRegistRouteProps {
searchParams: Promise<{
taskSeComCd?: string;
admdstCd?: string;
sido?: string;
gugun?: string;
}>;
}
export default async function Page({ searchParams }: InstlSncnRegistRouteProps) {
const params = await searchParams;
const taskSeComCd = params.taskSeComCd
? params.taskSeComCd.split(",").filter(Boolean)
: undefined;
return (
<InstlRegistPage
defaults={{
taskSeComCd,
admdstCd: params.admdstCd,
sido: params.sido,
gugun: params.gugun,
}}
/>
);
}
@@ -0,0 +1,3 @@
import { MyPbadmsSprtListPage } from "@/pages/my-pbadms-sprt";
export default MyPbadmsSprtListPage;
@@ -0,0 +1,3 @@
import { NtiHstryListPage } from "@/pages/nti-hstry";
export default NtiHstryListPage;
@@ -0,0 +1 @@
export { RvwMnpwMngPage as default } from "@/pages/rvw-mnpw-mng";
@@ -0,0 +1,13 @@
import { SewerBscPlanEditPage } from "@/pages/sewer-bsc-plan";
interface PageProps {
params: Promise<{ id: string }>;
searchParams: Promise<{ chgCycl?: string }>;
}
export default async function Page({ params, searchParams }: PageProps) {
const { id } = await params;
const { chgCycl } = await searchParams;
const propChgCycl = chgCycl ? Number(chgCycl) : undefined;
return <SewerBscPlanEditPage id={id} propChgCycl={propChgCycl} />;
}
@@ -0,0 +1,13 @@
import { SewerBscPlanDetailGate } from "@/pages/sewer-bsc-plan";
interface PageProps {
params: Promise<{ id: string }>;
searchParams: Promise<{ chgCycl?: string }>;
}
export default async function Page({ params, searchParams }: PageProps) {
const { id } = await params;
const { chgCycl } = await searchParams;
const propChgCycl = chgCycl ? Number(chgCycl) : undefined;
return <SewerBscPlanDetailGate id={id} propChgCycl={propChgCycl} />;
}
@@ -0,0 +1,3 @@
import { SewerBscPlanListPage } from "@/pages/sewer-bsc-plan";
export default SewerBscPlanListPage;
@@ -0,0 +1,19 @@
import { SewerBscPlanRegistPage } from "@/pages/sewer-bsc-plan";
interface PageProps {
searchParams: Promise<{
taskSeComCd?: string;
taskSeCdNm?: string;
sewerBscPlanSeComCd?: string;
sewerBscPlanSeCdNm?: string;
admdstCd?: string;
sido?: string;
gugun?: string;
}>;
}
export default async function Page({ searchParams }: PageProps) {
const params = await searchParams;
return <SewerBscPlanRegistPage defaults={params} />;
}
@@ -6,8 +6,8 @@ import { Providers } from "@/app/providers";
import "@/app/styles/globals.css";
export const metadata: Metadata = {
title: "행정지원시스템",
description: "행정지원시스템",
title: "디지털 행정지원 시스템",
description: "디지털 행정지원 시스템",
};
interface RootLayoutProps {
-1
View File
@@ -1 +0,0 @@
export { MainPage as default } from "@/pages/home";
+10
View File
@@ -0,0 +1,10 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { COOKIE_DOMAIN, REFRESH_TOKEN } from "@/shared/config";
export async function POST() {
const cookieStore = await cookies();
cookieStore.delete({ name: REFRESH_TOKEN, domain: COOKIE_DOMAIN });
return NextResponse.json({ message: "Logged out" }, { status: 200 });
}
+9 -9
View File
@@ -5,6 +5,7 @@ import {
ACCESS_TOKEN,
AUTH_USER_ID,
CLAIM_NAME,
COOKIE_DOMAIN,
REFRESH_TOKEN,
SERVER_API_URL,
SITE_ID,
@@ -20,6 +21,10 @@ async function handler(
"Content-Type": "application/json",
"X-Site-Id": SITE_ID,
};
const deviceId = request.headers.get("X-Device-Id");
if (deviceId) {
headers["X-Device-Id"] = deviceId;
}
let body = {};
const isRefreshHandler = params.url.includes("refresh");
@@ -38,7 +43,8 @@ async function handler(
body = await request.json();
}
const result = await fetch(`${SERVER_API_URL}/${params.url.join("/")}`, {
const targetUrl = `${SERVER_API_URL}/${params.url.join("/")}`;
const result = await fetch(targetUrl, {
method,
headers,
body: JSON.stringify(body),
@@ -58,18 +64,11 @@ async function handler(
[AUTH_USER_ID]: userId,
});
const forwardedHost = request.headers.get("x-forwarded-host");
const host = request.headers.get("host");
const actualHost = forwardedHost || host || new URL(request.url).hostname.toString();
// 운영 환경에서 domain 설정
const domain = actualHost.includes("hasuon.or.kr") ? ".hasuon.or.kr" : actualHost;
if (refreshToken) {
cookieStore.set(REFRESH_TOKEN, refreshToken, {
httpOnly: true,
sameSite: "strict",
domain: domain,
domain: COOKIE_DOMAIN,
path: "/",
});
}
@@ -79,6 +78,7 @@ async function handler(
return NextResponse.json({ message: "Invalid Credentials" }, { status: 401 });
} catch (error) {
console.error(`[Proxy API] Error caught in handler:`, error);
return NextResponse.json({ message: "Server Error", error }, { status: 500 });
}
}
+38
View File
@@ -0,0 +1,38 @@
import type { Metadata } from "next";
import Image from "next/image";
import "@/app/styles/globals.css";
export const metadata: Metadata = {
title: "디지털 행정지원 시스템",
description: "페이지가 존재하지 않습니다. 관리자에게 문의하여 주십시오.",
};
export default function GlobalNotFound() {
return (
<html lang="ko">
<body>
<main className="flex min-h-screen flex-col items-center justify-center bg-[#f0f4f8] px-4 py-16">
<div className="mb-16 text-center">
<h1 className="text-[3.2rem] font-bold leading-snug tracking-tight">
<span className="text-primary">디지털 행정지원 시스템</span>에 오신 것을 환영합니다.
</h1>
<p className="mt-6 text-[2rem] text-[#666]">오류가 발생하였습니다.</p>
</div>
<section className="card card-border flex w-full max-w-[72rem] flex-col items-center !rounded-2xl !py-16">
<Image
src="/images/magnifying-glass-with-warning.png"
alt=""
width={280}
height={280}
className="size-[28rem] object-contain"
/>
<p className="mt-4 text-[2.4rem] font-bold text-[#444]">페이지가 존재하지 않습니다.</p>
<p className="mt-2 text-[1.6rem] text-[#666]">관리자에게 문의하여 주십시오</p>
</section>
</main>
</body>
</html>
);
}
@@ -1,358 +0,0 @@
# 화면설계서: 결재선 관리
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 0.1 (초안) |
| 프로세스명 | UI설계 |
| 화면명 | 결재선 관리 |
| 화면 유형 | [v] 조회 [v] 입력 [v] 수정 [v] 삭제 [ ] 출력 |
| 메뉴 경로 | 디지털행정지원 > 시스템관리 > 결재선 관리 |
---
## 2. 화면 목적
전자결재 처리에 사용할 **결재서식**(템플릿)을 등록·관리하고, 각 서식별로 **결재선**(접수 -> 검토 -> 승인 -> 최종승인 단계)을 구성한다. 상단의 결재서식 목록에서 하나를 선택하면 하단 영역에 해당 서식의 결재 단계(결재자/순서/구분)가 표시되며, 사용자는 결재자 추가·수정·삭제와 결재선 저장을 수행한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ 페이지 타이틀: 결재선 관리 브레드크럼: 디지털행정지원 > … > │
├──────────────────────────────────────────────────────────────────────────────┤
│ [영역 A] 전자결재선 관리 목록 [서식 등록] │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ 전자결재서식아이디 | 결재서식명 | 문서구분 | 등록일시 | 관리 │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ TMPL-0001 (선택 행 강조) … [수정][삭제] │ │
│ │ TMPL-0002 … [수정][삭제] │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────────┤
│ [영역 B] 결재선 관리 선택된 서식: TMPL-0001 │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ [영역 B-1] 결재자 입력 폼 │ │
│ │ 결재선아이디(자동발급) | 결재구분 ▾ | 결재자명 [검토인력 선택] │ │
│ │ 결재순서번호 [결재자 추가] [결재선 저장] [초기화] │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ [영역 B-2] 결재선 그리드 │ │
│ │ 결재선아이디 | 결재순서번호 | 결재구분 | 결재자명 | 등록일시 | 관리 │ │
│ │ APLN-0001 | 1 | 접수 | 김민수 | … |[수정][삭제] │
│ │ APLN-0002 | 2 | 검토 | 박지훈 | … |[수정][삭제] │
│ │ APLN-0003 | 3 | 최종승인 | 이서연 | … |[수정][삭제] │
│ └──────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
CALL TBD.결재서식목록조회()
DISPLAY 결재서식 그리드 (영역 A)
DISPLAY 빈 상태 안내 (영역 B) // 서식 미선택 시 폼·그리드 비활성
// 영역 A: 서식 행 선택
ON 영역 A 행 CLICK DO
SET 선택된 서식 = 행.전자결재서식아이디 // 영역 B 상단 라벨에 표시
CALL TBD.결재선목록조회(서식아이디)
DISPLAY 결재선 그리드 (영역 B-2)
RESET 결재자 입력 폼 (영역 B-1)
END ON
// 영역 A: 신규 서식 등록
ON [서식 등록] 버튼 CLICK DO
OPEN 결재서식 등록 다이얼로그
ON 확인 DO
CALL TBD.결재서식등록(서식정보)
CALL TBD.결재서식목록조회() // → 영역 A 갱신
END ON
END ON
// 영역 A: 서식 수정·삭제
ON 영역 A [수정] 버튼 CLICK DO
OPEN 결재서식 수정 다이얼로그(선택 행)
ON 확인 DO
CALL TBD.결재서식수정(서식아이디, 서식정보)
CALL TBD.결재서식목록조회()
END ON
END ON
ON 영역 A [삭제] 버튼 CLICK DO
CONFIRM "선택한 결재서식을 삭제하시겠습니까?"
CALL TBD.결재서식삭제(서식아이디)
CALL TBD.결재서식목록조회()
IF 삭제된 서식이 현재 선택된 서식 THEN
RESET 영역 B (그리드 비우기, 폼 비활성)
END IF
END ON
// 영역 B-1: 결재자 입력 폼
ON [검토인력 선택] 버튼 CLICK DO
VALIDATE 서식 선택됨 // 미선택 시 안내 후 종료
OPEN 검토인력 선택 다이얼로그
ON 선택 DO
SET 결재자명 = 선택인력.성명
SET 결재자ID = 선택인력.사용자ID // 내부 상태로 보관
END ON
END ON
ON 결재구분 SELECT CHANGE DO
SET 결재구분 = 선택값 // 접수 / 검토 / 최종승인
END ON
ON 결재순서번호 INPUT CHANGE DO
SET 결재순서번호 = 값(숫자)
END ON
// 영역 B-1: 결재자 추가 (그리드에 행 추가, 미저장 상태)
ON [결재자 추가] 버튼 CLICK DO
VALIDATE 서식 선택됨
· 결재구분 입력
· 결재자 선택됨
· 결재순서번호 > 0 (중복 시 안내)
ADD 입력 폼 값을 영역 B-2 그리드에 임시 행으로 추가
RESET 결재자 입력 폼 (영역 B-1)
// 결재선아이디는 저장 시점에 서버에서 자동 발급되므로
// 임시 행은 클라이언트 임시 키로 식별 (예: APLN-0007 같은 미발급 표기 또는 임시 ID)
END ON
// 영역 B-1: 결재선 저장 (그리드 전체 일괄 저장)
ON [결재선 저장] 버튼 CLICK DO
VALIDATE 서식 선택됨 · 결재선 1건 이상 · 순서번호 중복 없음
CALL TBD.결재선일괄저장(서식아이디, 결재선목록)
CALL TBD.결재선목록조회(서식아이디) // → 영역 B-2 갱신
TOAST "결재선이 저장되었습니다."
END ON
// 영역 B-1: 초기화
ON [초기화] 버튼 CLICK DO
RESET 결재자 입력 폼 (영역 B-1) → 기본값
(결재구분=접수, 결재자명="", 결재순서번호=1)
END ON
// 영역 B-2: 그리드 행 수정·삭제
ON 영역 B-2 [수정] 버튼 CLICK DO
OPEN 결재자 수정 다이얼로그(선택 행)
ON 확인 DO
CALL TBD.결재선수정(결재선아이디, 결재선정보)
CALL TBD.결재선목록조회(서식아이디)
END ON
END ON
ON 영역 B-2 [삭제] 버튼 CLICK DO
CONFIRM "선택한 결재자를 삭제하시겠습니까?"
CALL TBD.결재선삭제(결재선아이디)
CALL TBD.결재선목록조회(서식아이디)
END ON
END
```
---
## 4. 영역 A: 전자결재선 관리 목록
선택된 결재서식이 영역 B의 작업 대상이 된다. 행 클릭으로 선택 상태를 표시(좌측 컬러 바·배경 강조)한다.
### 4-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | ----------------------------------------------- |
| 섹션 타이틀 | 전자결재선 관리 목록 |
| 우측 버튼 | **[서식 등록]** — 결재서식 등록 다이얼로그 오픈 |
### 4-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| ------------------ | ----------------- | ------ | --------------------------------------------- |
| 전자결재서식아이디 | `elctAtrzTmpltId` | string | 예) `TMPL-0001` (서버 자동 발급, TBD) |
| 결재서식명 | `atrzTmpltNm` | string | 예) `설치인가 검토 결재` |
| 문서구분 | `docSeNm` | string | 예) `설치인가`, `기본계획` (코드/명 매핑 TBD) |
| 등록일시 | `regDt` | string | `YYYY-MM-DD HH:mm:ss` |
| 관리 | — | — | 행별 **[수정] · [삭제]** 버튼 |
- 행 클릭 시: 해당 서식을 **선택 상태**로 만들고 영역 B 라벨(`선택된 서식: {전자결재서식아이디}`)과 영역 B-2 그리드를 갱신한다.
- 페이지네이션: TBD (스크린샷에는 미표시).
### 4-3. 버튼
| 버튼명 | 위치 | 동작 |
| --------- | -------------- | ----------------------------------------------------------------- |
| 서식 등록 | 섹션 우측 상단 | 결재서식 등록 다이얼로그 오픈 (입력 항목·필수값 TBD) |
| 수정 | 행별 | 결재서식 수정 다이얼로그 오픈 (해당 행 값으로 초기화) |
| 삭제 | 행별 | 확인 다이얼로그 후 삭제. 선택 중인 서식이면 영역 B 상태도 초기화. |
### 4-4. 다이얼로그(개요)
| 다이얼로그명 | 트리거 | 입력 항목(예상) | 비고 |
| ------------------ | ---------------- | ------------------------------ | ------------------------ |
| 결재서식 등록 | [서식 등록] 클릭 | 결재서식명, 문서구분(드롭다운) | 등록 성공 시 영역 A 갱신 |
| 결재서식 수정 | 행별 [수정] | 결재서식명, 문서구분 | 기존 값으로 초기화 |
| 결재서식 삭제 확인 | 행별 [삭제] | (확인/취소) | |
---
## 5. 영역 B: 결재선 관리
영역 A에서 선택된 결재서식을 기준으로 결재 단계(접수·검토·최종승인)와 결재자, 순서를 관리한다. 영역 B는 상단 **결재자 입력 폼(B-1)** 과 하단 **결재선 그리드(B-2)** 로 구성된다.
### 5-1. 헤더 / 상태 표시
| 항목 | 내용 |
| ----------- | ------------------------------------------------------------------------------------------------------------ |
| 섹션 타이틀 | 결재선 관리 |
| 우측 라벨 | `선택된 서식: {전자결재서식아이디}` — 영역 A에서 선택한 서식 ID를 강조 색으로 표시. 미선택 시 영역 B 비활성. |
### 5-2. 영역 B-1: 결재자 입력 폼
#### 필드 정의
| 표시명 | 입력 유형 | 필수 | 기본값 | 데이터 소스 | API 파라미터 매핑 | 비고 |
| ---------------------- | ----------------- | ---- | ----------- | -------------------- | ----------------- | --------------------------------------------------------------------- |
| 결재선아이디(자동발급) | 텍스트(읽기 전용) | — | `APLN-XXXX` | 서버 자동 발급 (TBD) | TBD | 입력 불가. 저장 시 서버 발급. 표시값은 미발급 자리표시(또는 임시 ID). |
| 결재구분 | 드롭다운 | 예 | `접수` | 코드 (TBD) | TBD | 옵션: **접수 / 검토 / 최종승인** (그리드 행 기준) |
| 결재자명 | 텍스트(읽기 전용) | 예 | `""` | [검토인력 선택] 결과 | TBD | 자유 입력 불가. 다이얼로그 선택 결과로 채움. |
| 결재순서번호 | 숫자 입력 | 예 | `1` | — | TBD | 1 이상의 정수. 동일 서식 내 중복 검증 필요. |
플레이스홀더 / 표시 텍스트:
- 결재자명 입력란 플레이스홀더: `검토인력을 선택하세요`
- 결재선아이디 표시 예시: `APLN-0007` (다음 발급 예정 번호 노출 또는 빈 칸 — 표시 정책 TBD)
#### 버튼
| 버튼명 | 동작 |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| 검토인력 선택 | **검토인력 선택 다이얼로그** 오픈. 선택 시 `결재자명`과 내부 `결재자 ID`를 폼 상태에 반영. |
| 결재자 추가 | 입력 폼 값을 영역 B-2 그리드에 **임시 행**으로 추가하고 폼을 초기화한다. (서버 호출 없음, 저장 시 일괄 반영.) |
| 결재선 저장 | 영역 B-2의 결재선 목록을 일괄 저장 API로 전송한다. 성공 시 그리드 재조회 + 토스트 표시. |
| 초기화 | 입력 폼을 기본값으로 리셋(결재구분=접수, 결재자명=빈값, 결재순서번호=1). |
> 정책 메모: **[결재자 추가]** 와 **[결재선 저장]** 의 분리에 대해서는 두 가지 운영이 가능하다. 본 SPEC 초안은 그리드에 임시 행을 누적해 두고 [결재선 저장] 시점에 서버에 일괄 반영하는 모델을 가정한다. 실제 구현 방식(추가 즉시 저장 vs 일괄 저장)은 백엔드 API 확정 후 재정의(TBD).
#### 다이얼로그
| 다이얼로그명 | 트리거 | 표시 내용 / 입력 항목 | 결과 |
| ---------------- | --------------- | ---------------------------------------------------------- | ----------------------------------- |
| 검토인력 선택 | [검토인력 선택] | 사용자 검색 필터(이름/소속 등 TBD), 사용자 목록(단일 선택) | 선택된 사용자 정보를 입력 폼에 반영 |
| 결재자 수정 | B-2 행별 [수정] | 결재구분, 결재자명(선택), 결재순서번호 | 확인 시 해당 행 갱신 |
| 결재자 삭제 확인 | B-2 행별 [삭제] | (확인/취소) | 확인 시 해당 행 삭제 |
### 5-3. 영역 B-2: 결재선 그리드
#### 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| ------------ | ------------ | ------ | ------------------------------------- |
| 결재선아이디 | `atrzLineId` | string | 예) `APLN-0001` (서버 자동 발급, TBD) |
| 결재순서번호 | `atrzSeqNo` | number | 1부터 시작. 단계 순서. |
| 결재구분 | `atrzSeNm` | string | `접수` / `검토` / `최종승인` |
| 결재자명 | `atrzUserNm` | string | 검토인력 선택 시 채워짐 |
| 등록일시 | `regDt` | string | `YYYY-MM-DD HH:mm:ss` |
| 관리 | — | — | 행별 **[수정] · [삭제]** 버튼 |
- 표시 정렬: 기본 `결재순서번호` 오름차순.
- 임시 행(서버 미저장)과 저장된 행의 시각적 구분 필요(예: ID 컬럼 표기 또는 행 스타일). 세부 UI는 TBD.
- 페이지네이션: TBD.
---
## 6. API/DTO 명세
> 본 화면은 현재 UI 위주 초안으로, API/DTO는 백엔드 명세 확정 후 채운다.
### 6-1. 공통
| 항목 | 내용 |
| ----------- | ------------------------------------------- |
| Base prefix | TBD (예: `/server` 또는 결재 서비스 prefix) |
| 인증 | JWT 토큰 기반 (Authorization 헤더) |
| 클라이언트 | TBD (예: `@/entities/approval-line`) |
### 6-2. 전자결재서식 (영역 A)
| 기능 | Method | URL | Request DTO | Response DTO | API(클라이언트 함수명) |
| ------------------ | ------ | --- | ----------- | ------------ | ---------------------- |
| 결재서식 목록 조회 | TBD | TBD | TBD | TBD | TBD |
| 결재서식 등록 | TBD | TBD | TBD | TBD | TBD |
| 결재서식 수정 | TBD | TBD | TBD | TBD | TBD |
| 결재서식 삭제 | TBD | TBD | TBD | TBD | TBD |
### 6-3. 결재선 (영역 B)
| 기능 | Method | URL | Request DTO | Response DTO | API(클라이언트 함수명) |
| ------------------------ | ------ | --- | ------------------------------------ | ------------ | ---------------------- |
| 결재선 목록 조회(서식별) | TBD | TBD | `{ elctAtrzTmpltId }` (TBD) | TBD | TBD |
| 결재선 추가/일괄 저장 | TBD | TBD | `{ elctAtrzTmpltId, lines[] }` (TBD) | TBD | TBD |
| 결재선 수정 | TBD | TBD | TBD | TBD | TBD |
| 결재선 삭제 | TBD | TBD | TBD | TBD | TBD |
### 6-4. 검토인력 (다이얼로그)
| 기능 | Method | URL | Request DTO | Response DTO | API(클라이언트 함수명) |
| ------------------ | ------ | --- | --------------- | ------------ | ---------------------- |
| 검토인력 목록 조회 | TBD | TBD | TBD (검색 필터) | TBD | TBD |
---
## 7. UI 구현 가이드
### 7-1. 기술 스택·원칙
- **공통 컴포넌트**: `@common/components` (버튼, 셀렉트, 인풋, 카드 등 기존 패턴 재사용).
- **테이블**: `@tanstack/react-table`, 프로젝트 표준 `useDataTable` 패턴이 있다면 동일하게 적용.
- **데이터**: React Query 기반 조회/뮤테이션. API 확정 후 `queryKey` 와 무효화 정책 정리.
- **상태**: 영역 A의 **선택된 서식 ID**, 영역 B-1의 **결재자 입력 폼 값**, 영역 B-2의 **임시 결재선 배열**은 페이지 로컬 상태로 관리. 다이얼로그 상태(검토인력 선택 등)는 컴포넌트 로컬 상태 또는 가벼운 상태 관리 훅으로 분리.
- **모의 데이터**: 백엔드 명세 확정 전까지 `@faker-js/faker` 로 그리드 목데이터 구성(스크린샷 컬럼/값과 동일 형태).
### 7-2. 모듈·파일 배치(제안)
| 구분 | 경로 |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| 라우트(App Router) | `apps/as/app/(with-base-layout)/approval-line-management/page.tsx` |
| 페이지 컴포넌트 | `apps/as/src/pages/approval-line-management/index.ts`<br/>`.../ui/approval-line-management-page.tsx` |
| 위젯(영역 단위) | `apps/as/src/widgets/approval-template-list/`<br/>`apps/as/src/widgets/approval-line-editor/` |
| 피처(상호작용) | `apps/as/src/features/approver-picker/` (검토인력 선택 다이얼로그) |
| 엔티티(도메인) | `apps/as/src/entities/approval-template/`<br/>`apps/as/src/entities/approval-line/` |
| 그리드 컬럼 정의 | `entities/approval-template/model/approval-template-table-columns.tsx`<br/>`entities/approval-line/model/approval-line-table-columns.tsx` |
| API 클라이언트 | `entities/approval-template/api/*`, `entities/approval-line/api/*` (TBD) |
### 7-3. 영역별 UI 매핑
| 영역 | 주요 UI | 비고 |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| A | 섹션 헤더 + Button(`서식 등록`) + DataTable (행 선택, 행별 Action 버튼) | 선택 행 강조, 행 클릭으로 영역 B 갱신 |
| B 헤더 | 상태 라벨 `선택된 서식: {ID}` | 미선택 시 영역 B-1·B-2 비활성 |
| B-1 | 읽기 전용 Input(아이디), Select(결재구분), Input+Button(결재자/검토인력 선택), NumberInput(순서번호), Action 버튼 3종(`결재자 추가`/`결재선 저장`/`초기화`) | 폼 유효성 검증 후 그리드에 임시 행 추가 |
| B-2 | DataTable (행별 [수정][삭제]), 임시 행 시각적 구분 | 기본 정렬: 결재순서번호 ASC |
| 공통 | Dialog(검토인력 선택, 서식 등록/수정, 결재자 수정, 삭제 확인) | 디자인 시스템의 `Dialog` 컴포넌트 사용 |
### 7-4. 데이터 흐름(요약)
- **진입 시**: 결재서식 목록 조회(TBD) → 영역 A 그리드 렌더. 영역 B는 안내/비활성 상태.
- **서식 선택 시**: 선택된 `elctAtrzTmpltId` 를 페이지 상태로 보관 → 결재선 목록 조회(TBD) → 영역 B-2 그리드 갱신. 영역 B-1 폼 초기화.
- **결재자 추가**: 입력 폼 값을 영역 B-2의 **클라이언트 임시 배열**에 push. 결재선아이디는 미발급 상태로 표시(또는 임시 키). 폼 리셋.
- **결재선 저장**: 임시 배열 + 기존 그리드 행을 합쳐 일괄 저장 API(TBD) 호출 → 성공 시 그리드 재조회 + 토스트.
- **수정/삭제**: 각각의 단건 API(TBD) 호출 후 그리드 재조회.
- **검토인력 선택 다이얼로그**: 사용자 목록 조회(TBD) → 단일 선택 → 폼의 결재자명/결재자 ID 갱신.
- **서식 등록/수정/삭제**: 다이얼로그 또는 확인 후 단건 API(TBD) 호출 → 영역 A 그리드 재조회. 삭제 대상이 현재 선택된 서식이면 영역 B 초기화.
### 7-5. 미확정 / 검증 필요 항목 (TBD)
- 모든 백엔드 API 경로·DTO·필드명.
- `결재선아이디(자동발급)` 입력 박스의 표시 정책: (a) 다음 발급 예정 번호 미리 보여주기, (b) 저장 전에는 빈 칸 또는 자리표시자만, (c) 임시 ID.
- `[결재자 추가]` 의 동작: 그리드에 임시 누적(현재 가정) vs 즉시 단건 저장.
- 결재구분 코드 값(`접수`/`검토`/`최종승인`)의 코드 매핑.
- 문서구분 코드 값(`설치인가`/`기본계획` 등)의 코드 매핑.
- 영역 A·B 그리드의 페이지네이션·정렬·검색 필터 유무.
- 검토인력 선택 다이얼로그의 검색 필드(부서/직급/이름 등) 및 다중 선택 허용 여부.
- 결재선 저장 시 순서번호 중복·누락(예: 1, 2, 4) 허용 정책.
- 권한(역할별 등록/수정/삭제 가능 여부).
@@ -0,0 +1,250 @@
# 재원협의·실무협의 프론트엔드 개발 가이드
> **대상 앱**: `frontend/apps/as`
> **작성일**: 2026-08-27
> **개정일**: 2026-09-07
> **버전**: 2.0
> **Process**: PR-AS-003 (재원협의 관리 — 실무협의 및 AI 검증 통합)
>
> **SoT (2026-09-03 완결)**:
>
> - 프로세스·상태·버튼: `03.재원협의_실무협의_프로세스_통합_v완결.md`
> - REST 설계: `05.재원협의_상태별_endpoint_설계.md`
> - 공통코드: `FNRSSTTS` (`SV, ST, LE, FV, ER, SL, AC, TE, TP, RM, RB, RC, RA, AV, EV, RK, RR, EP, TA`)
>
> **백엔드 서비스**:
>
> - `as-service` — `/api/v1/fnrs-cnsltn-mng`, `/api/v1/pwrk-cnsltn-mng`, `/api/v1/pwrk-cnsltn-doc-crt-rslt`, `/api/v1/dcrag-to`
> - `as-elapr-service` — `/api/v1/elapr-dmnd`, `/api/v1/elapr-prgrs-stts`, `/api/v1/elapr-apln`
>
> **현재 프론트 상태**: 재원협의(`fnrs-cnsltn`)·전자결재(`elapr`)·DC RAG TO(`dcrag-to`)는 존재한다. 실무협의(`pwrk-cnsltn-`\*) 엔티티·화면은 **미구현**. 버튼 매트릭스·상태코드는 **v2.1 잔존**이며 v3.0(`FNRSSTTS`)과 불일치한다. [9절](#9-현재-코드-vs-to-be-갭) 참고.
---
## 1. 문서 목적
재원협의 4대 주체(기초·광역·환경청·공단)의 **상태 전이·버튼·API**를 `apps/as`에 구현할 때 쓰는 프론트엔드 가이드이다.
v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기**만 다뤘다. v2.0은 2026-09-03 완결 프로세스 기준으로 **신청부터 최종종결(**`TA`**)까지**, 실무협의·AI 3파이프라인·결재선·보완 루프를 포함한다.
상세 표·시퀀스·DTO는 본문에 넣지 않고 레퍼런스로 분리한다. 구현 중에는 본문의 규칙만 보고, 표가 필요하면 해당 파일을 연다.
---
## 2. 문서 맵
| 파일 | 내용 | 언제 보는가 |
| -------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------- |
| [development-guide.md](./development-guide.md) (본 문서) | 도메인, 핵심 규칙, 화면 흐름, 갭, 구현 순서 | 일상 구현 |
| [ref-process-status.md](./ref-process-status.md) | 4-Lane 시퀀스, Activity, `FNRSSTTS`, 보완 3루프, AI 타임아웃, 결재선 복사 | 상태 전이·폴링 UX |
| [ref-screen-buttons.md](./ref-screen-buttons.md) | 38행 화면·역할·버튼 매트릭스, 버튼 가시성 | `button-config.ts` 개편 |
| [ref-endpoint.md](./ref-endpoint.md) | 버튼→API 총괄, 단계별 입출력, FE 호출 금지 API, Gap Analysis | 엔티티·service 매핑 |
| [ref-frontend-entities.md](./ref-frontend-entities.md) | ENDPOINT, 슬라이스, Service 시그니처, 폴링, 웹기안기, DTO | 엔티티 레이어 구현 |
| [ref-webhwp-api.md](./ref-webhwp-api.md) | 웹한글기안기 v3 `HwpCtrl` Properties/Methods, Open/SaveAs | 기안기 위젯 연동 |
---
## 3. 도메인 개요
실무협의서(PWRK)는 재원협의서(FNRS)의 **하위 문서**이다. 재원협의 1건당 실무협의서는 **1건만** 생성된다.
| 문서 | 번호 예시 | 필드 |
| -------- | ----------------- | ----------------------------- |
| 재원협의 | `F-20260824-0001` | `fnrsCnsltnDocNo` / `upDocNo` |
| 실무협의 | `P-20260827-0001` | `pwrkCnsltnDocNo` |
진행상태는 **재원협의 마스터** `tb_as_fnrs_cnsltn_mng.prgrs_stts_com_cd` (`FNRSSTTS`)가 SSOT이다. 실무협의 문서의 `prgrsSttsComCd`는 연계 표시용이며, 화면 버튼은 재원협의 상태로 분기한다.
### 3-1. 4대 주체
| 역할 | 코드 | 화면 소유 | 핵심 구간 |
| --------------- | ------------- | -------------- | ------------------------------------ |
| 기초자치단체 | `ROLE_USER05` | 작성/수정/상세 | 신청, 3종 보완, 조치결과, 최종확인 |
| 광역자치단체 | `ROLE_USER04` | 상세 | 경유 제출, 결과 전달 |
| 유역·지방환경청 | `ROLE_USER03` | 상세 | 의뢰/자체종결, 1·2차 회신 |
| 한국환경공단 | `ROLE_USER01` | 상세 + 결재선 | 파일검증, 검토자지정, 1·2차 기술검토 |
### 3-2. 핵심 식별자
| 필드 | 의미 | 비고 |
| ----------------- | ---------------- | ----------------------------------------------------------- |
| `fnrsCnsltnDocNo` | 재원협의문서번호 | 상태 전이·목록 PK |
| `pwrkCnsltnDocNo` | 실무협의문서번호 | `P-YYYYMMDD-NNNN`. 검토자지정(`ER→AC`) 시 발급 |
| `upDocNo` | 상위문서번호 | 재원협의 번호. `upDocSeComCd = "FNRS"` |
| `crtDmndUuid` | 생성요청 UUID | 초안 폴링·웹기안기·다운로드 키 |
| `verSn` | 버전일련번호 | 복합 PK `(crtDmndUuid, verSn)`. 저장 시 서버가 `+1` |
| `orgnlOtptDocCn` | HWP 바이너리 | 목록/상세 응답은 항상 `null`. 다운로드·`webhwp/open`만 사용 |
| `elaprCycl` | 결재 차수 | 1차 `TE`, 2차 `RK`. 금액검증 완료 후 1차 결재선 복사 |
### 3-3. IIPBS 약어
| 한글 | 약어 | 필드 예 |
| ---- | ------------ | --------------------------- |
| 재원 | FNRS | `fnrsCnsltnDocNo` |
| 실무 | PWRK | `pwrkCnsltnDocNo` |
| 협의 | CNSLTN | `fnrs-cnsltn-mng` |
| 생성 | CRT | `crtDmndUuid` |
| 요청 | DMND | `crtDmndUuid`, `elapr-dmnd` |
| 검증 | VRFC | `vrfcSttsComCd` |
| 보완 | SPLMNT | `splmntDmndList` |
| 결재 | ELAPR / ATRZ | `elaprCycl`, `atrzSeComCd` |
---
## 4. 상태코드 한눈에 보기
상위코드 `FNRSSTTS`. v1.0의 `PWRKSTTS`(`DV`, `AR`, `TR` 등)는 **폐기**한다.
정방향:
```
SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → EV → RK → RR → EP → TA
```
자체종결: `LE → EP → TA`
보완: `ER|TE|RK → SL` 후 직전 상태로 복귀.
| 코드 | 명칭 | FE에서 할 일 |
| ---- | ---------------------- | ------------------------------------------------- |
| `SV` | 저장 | 기초 작성화면 저장/신청/삭제 |
| `ST` | 신청완료 | 광역 환경청제출 |
| `LE` | 환경청 검토 | 자체종결(`EP`) 또는 기술검토 의뢰(`FV`) |
| `FV` | 파일검증 | AI 진행 표시. **30분** 후 `ER` Fallback |
| `ER` | 기술검토 의뢰 | 공단 접수자: 파일보완 / 수동파일검증 / 검토자지정 |
| `SL` | 보완요청 | 기초 수정화면. 복귀 대상은 이력의 직전 상태 |
| `AC` | 초안생성 | AI 진행 표시. **30분** 후 `TE` Fallback |
| `TE` | 기술검토 | 웹기안기·검토완료·보완. 결재선 1차 |
| `TP` | 기술검토 회신 | 환경청 → 광역 `RM` |
| `RM` | 재원협의결과회신 | 광역 → 기초 `RB` |
| `RB` | 지자체회신 | 기초 실무협의서 관리 / 조치완료 |
| `RC` | 조치결과서 제출 | 광역 → 환경청 `RA` |
| `RA` | 조치결과서제출(환경청) | 환경청 2차 의뢰 `AV` |
| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `EV` Fallback |
| `EV` | 조치결과검토의뢰 | 공단 접수자: 2차 결재선 검토자지정 → `RK` |
| `RK` | 조치결과서검토 | 웹기안기·금액검증 재실행·보완. 결재선 2차 |
| `RR` | 조치결과서검토 회신 | 환경청 → 광역 `EP` |
| `EP` | 검토결과 회신 | 광역 → 기초 `TA` |
| `TA` | 검토결과 회신(종료) | 기초 조회만. **고시(**`TB`**) 아님** |
코드 의미·구버전 대조·보완 루프·AI Fallback은 [ref-process-status.md](./ref-process-status.md).
---
## 5. 프론트가 지켜야 할 핵심 규칙
1. **상태 SSOT는** `FNRSSTTS`. 버튼 가시성은 `(dtlInstCd, prgrsSttsComCd, atrzSeComCd)`로만 계산한다. 공단은 결재선 구분(`ATRZ01` 접수자, `ATRZ02` 검토자, `ATRZ03` 승인자, `ATRZ99` 최종승인자)을 추가로 본다.
2. **AI 콜백 API는 화면에서 호출하지 않는다.** `POST /dcrag/documents/verify-result`, `POST /dcrag/draft/complete`, `POST /dcrag/cost/verify-result`는 서버 전용이다.
3. **초안생성 1차 트리거는 검토자지정이다.** `POST /elapr-dmnd` (`ER → AC`). 예전처럼 `POST /pwrk-cnsltn-mng`로 초안을 시작하지 않는다.
4. **기술검토 의뢰는** `LE → FV` **전용 API** `POST /fnrs-cnsltn-mng/{docNo}/request-examine`. 현재 코드의 `updatePrgrsStts(ER)` 직행은 폐기.
5. **수동 AI 재실행은 상태를 바꾸지 않는다.**
- `ER` 파일검증 → `POST /dcrag-to/documents/transfer`
- `TE` 초안생성 → `POST /dcrag-to/documents/draft/generate` (타임아웃·실패 시에만)
- `RK` 금액검증 → `POST /dcrag-to/documents/cost/verify`
6. **진행상태만 바꾸는 버튼은** `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts`**를 재사용한다.** (`ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`)
7. **보완요청/보완완료는 공통 API** `reqsupplement` / `submitsupplement`. 복귀 상태(`ER`/`TE`/`RK`)는 서버가 이력에서 결정한다. 프론트는 직전 상태를 추측해 보내지 않는다.
8. **결재 승인/회신은** `PUT /elapr-prgrs-stts/{id}/process` **하나**. 1차 최종승인자 → `TP`, 2차 → `RR`은 서버가 Feign으로 처리한다.
9. **2차 결재선은 EV에서 수동 검토자지정**. `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선 자동 copy-line은 사용하지 않는다.
10. **웹기안기는 `webhwp/open` 응답을 axios `blob`으로 조회한 뒤 `File`로 감싸 `HwpCtrl.Open`에 전달한다.** `blob:` Object URL과 `orgnlOtptDocCn`은 쓰지 않는다.
---
## 6. 화면 데이터 흐름
재원협의 **목록 → 상세/작성**이 본체다. 실무협의 목록은 공단 검토 보조 화면이다.
```
BEGIN
CALL fnrsCnsltnMngService.findPage(...)
ON 행 CLICK → /fnrs-cnsltn/{fnrsCnsltnDocNo}
CALL findById(fnrsCnsltnDocNo)
COMPUTE buttons = getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList)
// ----- 기초 ROLE_USER05 -----
ON [저장] (빈값/SV) → saveApply(prgrsSttsComCd: SV)
ON [신청완료] (SV) → saveApply(prgrsSttsComCd: ST)
ON [삭제] (SV) → remove
ON [파일보완완료] (SL←ER) → submitsupplement
ON [보완완료] (SL←TE|RK) → submitsupplement
ON [실무협의서 관리] (RB) → GET webhwp/open (blob) → HwpCtrl.Open(File)
ON [조치완료] (RB) → submit-actn-result { isSubmit: true }
ON [결과회신] (TA) → API 없음 (조회)
// ----- 광역 ROLE_USER04 -----
ON [환경청제출] (ST) → updatePrgrsStts(LE)
ON [지자체회신] (RM) → updatePrgrsStts(RB)
ON [조치결과서제출] (RC) → updatePrgrsStts(RA)
ON [결과회신] (EP) → updatePrgrsStts(TA)
// ----- 환경청 ROLE_USER03 -----
ON [결과회신] (LE 자체종결) → tech-review → EP
ON [기술검토 의뢰] (LE) → request-examine → FV → START POLL FV
ON [재원협의 결과 회신] (TP) → tech-review → RM
ON [실무협의서 관리] (RA) → GET webhwp/open (blob) → HwpCtrl.Open(File) (저장 불가)
ON [조치결과 검토의뢰] (RA) → request-actn-review → AV → START POLL AV
ON [결과회신] (RR) → updatePrgrsStts(EP)
// ----- 공단 ROLE_USER01 -----
ON [파일보완] (ER, ATRZ01) → GET file-vrfc-results → 팝업 → reqsupplement
ON [파일검증] (ER, ATRZ01) → dcrag-to transfer (상태 유지)
ON [검토자지정] (ER, ATRZ01) → POST elapr-dmnd (docPrgrsSttsComCd: AC) → START POLL AC
ON [검토자지정] (EV, ATRZ01) → POST elapr-dmnd (elaprCycl: 2, docPrgrsSttsComCd: RK)
ON [실무협의서관리] (TE|RK) → GET webhwp/open (blob) → HwpCtrl.Open(File)
ON [실무협의서생성] (TE, 실패/타임아웃) → dcrag-to draft/generate
ON [검토결과조회] (TE) → pwrk-cnsltn-mng findById | findByUpDocNo
ON [저장] (TE|RK) → pwrk-cnsltn-mng save-apply
ON [검토완료] → elapr-prgrs-stts process (상신)
ON [보완요청] (TE|RK) → 보완 팝업 → reqsupplement
ON [금액검증] (RK) → dcrag-to cost/verify
ON [결재(승인)] → process (중간)
ON [결재(회신)] → process (최종 → TP 또는 RR)
// ----- AI 진행 중 (FV / AC / AV) -----
POLL 상태 또는 draft status. 30분 안내. 종료 후 상세 invalidate
END
```
38행 버튼 표는 [ref-screen-buttons.md](./ref-screen-buttons.md). API 상세는 [ref-endpoint.md](./ref-endpoint.md).
---
## 7. 버튼 → API 축약
프론트가 치는 것만. 콜백·스케줄러는 제외.
| 주체 | 버튼 | From → To | Method & URI |
| --------- | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------- |
| 기초 | 저장 / 신청완료 | →`SV` / `SV→ST` | `POST /fnrs-cnsltn-mng/save-apply` |
| 기초 | 삭제 | `SV` | `DELETE /fnrs-cnsltn-mng/{docNo}` |
| 광역 | 환경청제출 | `ST→LE` | `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` |
| 환경청 | 결과회신(자체종결) | `LE→EP` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
| 환경청 | 기술검토 의뢰 | `LE→FV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-examine` |
| 공단 접수 | 파일보완 팝업 | `ER` | `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results` |
| 공단 접수 | 파일보완요청 | `ER→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
| 공단 접수 | 파일검증 재실행 | `ER` 유지 | `POST /dcrag-to/documents/transfer` |
| 공단 접수 | 검토자지정 | `ER→AC` | `POST /elapr-dmnd` (`elaprCycl: 1`) |
| 공단 접수 | 검토자지정 | `EV→RK` | `POST /elapr-dmnd` (`elaprCycl: 2`) |
| 공단 검토 | 실무협의서관리 | `TE`/`RK` | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
| 공단 검토 | 실무협의서생성 | `TE` 유지 | `POST /dcrag-to/documents/draft/generate` |
| 공단 검토 | 저장 | `TE`/`RK` | `POST /pwrk-cnsltn-mng/save-apply` |
| 공단 | 검토완료 / 결재(승인·회신) | `TE` 유지 / `TE→TP` / `RK→RR` | `PUT /elapr-prgrs-stts/{id}/process` |
| 공단 검토 | 보완요청 | `TE→SL` / `RK→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
| 기초 | 파일보완완료 / 보완완료 | `SL→ER` / `TE` / `RK` | `POST /fnrs-cnsltn-mng/{docNo}/submitsupplement` |
| 환경청 | 재원협의 결과 회신 | `TP→RM` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
| 광역 | 지자체회신 | `RM→RB` | `updatePrgrsStts` |
| 기초 | 실무협의서 관리 | `RB` 유지 | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
| 기초 | 조치완료 | `RB→RA` | **신규** `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit: true`) |
| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` |
| 환경청 | 실무협의서 관리 | `RA` 유지 (조회) | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
| 환경청 | 조치결과 검토의뢰 | `RA→AV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` |
| 공단 검토 | 금액검증 재실행 | `RK` 유지 | `POST /dcrag-to/documents/cost/verify` |
| 환경청 | 결과회신 | `RR→EP` | `updatePrgrsStts` |
| 광역 | 결과회신 | `EP→TA` | `updatePrgrsStts` |
| 웹기안기 | 저장 | 버전 +1 | `POST /pwrk-cnsltn-doc-crt-rslt` multipart |
| 공통 | 다운로드 | — | `GET /pwrk-cnsltn-doc-crt-rslt/{uuid}/download` |
신규 4개: `request-examine`, `submit-actn-result`, `request-actn-review`, EV 2차 `elapr-dmnd`.
---
+334
View File
@@ -0,0 +1,334 @@
# 레퍼런스 — 상태별 Endpoint
> 상위: [개발가이드.md](./개발가이드.md)
> SoT: `05.재원협의_상태별_endpoint_설계.md` (v1.0, 2026-09-03)
> Gateway: `/as-service`, `/as-elapr-service`
> Auth: `Authorization`, `X-Site-Id`, `token-id` (기존 `client` 인터셉터)
> 날짜: `yyyy-MM-dd HH:mm:ss`
프론트가 치는 API만 상세히 적는다. AI 콜백·스케줄러·Feign은 [§6 호출 금지](#6-프론트-호출-금지).
---
## 1. 총괄 매트릭스 (38 + WebHWP)
| 순번 | 버튼 | From → To | 서비스 | Method & URI | 구분 |
| :---: | --- | --- | --- | --- | --- |
| 1 | 저장 | →`SV` | as | `POST /api/v1/fnrs-cnsltn-mng/save-apply` | 기존 |
| 2 | 신청완료 | `SV→ST` | as | 동일 (`prgrsSttsComCd=ST`) | 기존 |
| 3 | 삭제 | `SV` | as | `DELETE /api/v1/fnrs-cnsltn-mng/{docNo}` | 기존 |
| 4 | 환경청제출 | `ST→LE` | as | `POST /api/v1/fnrs-cnsltn-mng/doc/updatePrgrsStts` | 기존 |
| 5 | 결과회신(자체종결) | `LE→EP` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/tech-review` | 기존 |
| 6 | 기술검토 의뢰 | `LE→FV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-examine` | **신규** |
| 7 | 파일검증 콜백 | `FV→ER` | as | `POST /api/v1/dcrag/documents/verify-result` | 서버 |
| 8 | 파일보완 팝업 | `ER` | as | `GET /api/v1/fnrs-cnsltn-mng/{docNo}/file-vrfc-results` | 기존 |
| 9 | 파일보완요청 | `ER→SL` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/reqsupplement` | 기존 |
| 10 | 파일검증 재실행 | `ER` | as | `POST /api/v1/dcrag-to/documents/transfer` | 기존 |
| 11 | 검토자지정 | `ER→AC` | elapr | `POST /api/v1/elapr-dmnd` | 기존 |
| 12 | 초안 콜백 | `AC→TE` | as | `POST /api/v1/dcrag/draft/complete` | 서버 |
| 13 | 실무협의서관리 | `TE` | as | `GET /api/v1/pwrk-cnsltn-mng/updocno/{upDocNo}` | 기존 |
| 14 | 검토결과조회 | `TE` | as | `GET /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
| 15 | 실무협의서생성 | `TE` | as | `POST /api/v1/dcrag-to/documents/draft/generate` | 기존 |
| 16 | 저장(실무) | `TE` | as | `POST /api/v1/pwrk-cnsltn-mng/save-apply` | 기존 |
| 17 | 검토완료 | `TE` | elapr | `PUT /api/v1/elapr-prgrs-stts/{id}/process` | 기존 |
| 18 | 보완요청 | `TE→SL` | as | `POST .../reqsupplement` | 기존 |
| 19 | 결재(승인) | `TE` | elapr | `PUT .../process` | 기존 |
| 20 | 결재(회신) | `TE→TP` | elapr | `PUT .../process` | 기존 |
| 21 | 파일보완완료 | `SL→ER` | as | `POST .../submitsupplement` | 기존 |
| 22 | 보완완료(기술) | `SL→TE` | as | 동일 | 기존 |
| 23 | 보완완료(조치) | `SL→RK` | as | 동일 | 기존 |
| 24 | 재원협의 결과 회신 | `TP→RM` | as | `POST .../{docNo}/tech-review` | 기존 |
| 25 | 지자체회신 | `RM→RB` | as | `POST .../doc/updatePrgrsStts` | 기존 |
| 26 | 실무협의서 관리 | `RB` | as | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | 기존 |
| 27 | 조치완료 | `RB→RA` | as | `POST /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit=true`) | **신규** |
| 28 | 조치결과서제출 | `RC→RA` | as | `updatePrgrsStts` | 기존 |
| 29 | 조치결과 검토의뢰 | `RA→AV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-actn-review` | **신규** |
| 30 | 금액검증 콜백 | `AV→EV` | as | `POST /api/v1/dcrag/cost/verify-result` | 서버 |
| 30a | 검토자지정(2차) | `EV→RK` | elapr | `POST /api/v1/elapr-dmnd` (`elaprCycl=2`) | 기존 |
| 31 | 실무협의서관리 | `RK` | as | `GET /pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
| 32 | 금액검증 재실행 | `RK` | as | `POST /api/v1/dcrag-to/documents/cost/verify` | 기존 |
| 33 | 보완요청(금액) | `RK→SL` | as | `reqsupplement` | 기존 |
| 34 | 결재(승인) | `RK` | elapr | `process` | 기존 |
| 35 | 결재(회신) | `RK→RR` | elapr | `process` | 기존 |
| 36 | 결과회신 | `RR→EP` | as | `updatePrgrsStts` | 기존 |
| 37 | 결과회신 | `EP→TA` | as | `updatePrgrsStts` | 기존 |
| 38 | 결과회신(확인) | `TA` | — | 없음 | — |
| W1 | 웹기안기 Open | — | as | `GET /api/v1/pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | 기존 |
| W2 | HWPX 저장 | — | as | `POST /api/v1/pwrk-cnsltn-doc-crt-rslt` multipart | 기존 |
| W3 | 다운로드 | — | as | `GET /api/v1/pwrk-cnsltn-doc-crt-rslt/{uuid}/download` | 기존 |
| E-N | 결재선 복사 | `AV→RK` 부수 | elapr | `POST /api/v1/elapr-apln/copy-line` | **신규·서버** |
조회 공통: `GET /fnrs-cnsltn-mng`, `GET /fnrs-cnsltn-mng/{docNo}`, `GET /pwrk-cnsltn-mng`, `GET /dcrag-to/documents/draft/status/{crtDmndUuid}`.
---
## 2. 공통 요청
### `DocPrgrsUpdateRequestDto`
`POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` — `ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`.
```json
{
"docNo": "F-20260824-0001",
"docSeComCd": "FNRS",
"prgrsSttsComCd": "LE",
"rgtrUuid": "optional-uuid",
"rmrk": "optional"
}
```
응답: `204/200` void. 프론트 엔티티: 기존 `changePrgrsSttsComCd`.
### `FnrsCnsltnApplySaveRequestDto`
`save-apply`, `tech-review`, `reqsupplement`, `submitsupplement`가 공유. 핵심은 `fnrsCnsltnMng.prgrsSttsComCd`와 중첩 리스트(`bzScaleList`, `bizCstList`, `cklstResultList`, `esntlAtchFileList`, `splmntDmndList`).
---
## 3. 단계별 FE API
### 3-1. `SV → ST → LE`
**저장 / 신청완료** — `POST /fnrs-cnsltn-mng/save-apply`
권한: `ROLE_USER05`. 임시저장은 `prgrsSttsComCd=SV`, 신청은 `ST`.
응답: `string` (문서번호). 기존 `fnrsCnsltnMngService.saveApply`.
**삭제** — `DELETE /fnrs-cnsltn-mng/{docNo}` (`SV`만).
**환경청제출** — `updatePrgrsStts` `LE`. 권한: `ROLE_USER04`.
### 3-2. `LE → FV | EP`
**자체종결** — `POST /fnrs-cnsltn-mng/{docNo}/tech-review`
Body: `FnrsCnsltnApplySaveRequestDto` (환경청 의견·체크리스트). `LE→EP`.
기존 `techReview`. 현재 화면은 `TP`에서도 이 API를 쓰며 목표 상태를 `EP`로 넣고 있다 → **`TP`는 `RM`으로 바꿔야 함**.
**기술검토 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-examine`
```json
{ "prgrsSttsComCd": "FV" }
```
응답: `string` (docNo). `LE→FV` + AI 파일검증 + 30분 타이머(서버).
프론트: `fnrsCnsltnMngService.requestExamine(docNo, body)` 추가. **`updatePrgrsStts("ER")` 금지.**
### 3-3. `FV → ER → SL → ER`
파일검증 콜백은 서버. FE는 `FV`에서 상세 refetch.
**검증결과 목록** — `GET /fnrs-cnsltn-mng/{fnrsCnsltnDocNo}/file-vrfc-results`
응답: `FnrsCnsltnFileVrfcRsltResponseDto[]`
| 필드 | 설명 |
| --- | --- |
| `fileSeComCd` | `ESSENTIAL` / `ADDITIONAL` |
| `fileSeNm` | 구분명 |
| `esntlAtchFileId` | 필수첨부 ID |
| `atchFileNm` | 파일명 |
| `vrfcRsltComCd` | `PASS` / `FAIL` / `WARN` |
| `vrfcOpnn` | 검증의견 |
엔티티에 `findFileVrfcResults` 추가. 첨부 행 배지는 상세 `esntlFileList`/`etcFileList`의 `vrfcSttsComCd`, `vrfcRsltCn`, `vrfcDt`도 사용 (타입 확장 필요).
**파일보완요청** — `POST .../{docNo}/reqsupplement` (`ER→SL`).
**파일보완완료** — `POST .../{docNo}/submitsupplement` (`SL→ER`). 복귀는 서버.
**파일검증 재실행** — `POST /dcrag-to/documents/transfer`
```json
{ "fnrsCnsltnDocNo": "F-20260824-0001", "taskSeComCd": "SF", "taskSeCdNm": "하수처리시설" }
```
응답: `{ resultCode, message, requestId }`. 상태 유지. 기존 `dcRagApiToService.requestDocumentTransfer`.
### 3-4. `ER → AC → TE → TP`
**검토자지정** — `POST /elapr-dmnd`
기존 `elaprDmndService.save`.
| 필드 | 값 |
| --- | --- |
| `elaprDocNo` | 재원협의 번호 |
| `elaprCycl` | `1` |
| `docPrgrsSttsComCd` | **`AC`** (현행은 현재 상태 `ER`를 넣고 있음 → 수정) |
| `elaprPrgrsSttsList[].atrzOrdr` | 1 검토자, 2 승인자, 3 최종 |
서버가 실무협의 문서 생성 + RAG 초안 트리거 + 30분 타이머. 응답의 문서번호/`crtDmndUuid`가 있으면 폴링 키로 쓴다. 없으면 `findByUpDocNo`로 복원.
**초안 상태 폴링** — `GET /dcrag-to/documents/draft/status/{crtDmndUuid}`
기존 `getDraftGenerationStatus`. 주기 5~10초. 종료 `COMPLETED`/`FAILED`.
**수동 초안생성** — `POST /dcrag-to/documents/draft/generate` (`TE` 유지)
```json
{
"fnrsCnsltnDocNo": "F-...",
"pwrkCnsltnDocNo": "P-...",
"crtDmndUuid": "optional"
}
```
202 + `DcRagDraftGenerateResponseDto` (`crtDmndUuid`, `crtSttsComCd`, `estimatedMinutes`).
v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**. 1차 트리거는 검토자지정이고, 이 API는 Fallback 전용.
**실무 저장** — `POST /pwrk-cnsltn-mng/save-apply` → `string`.
**보완요청/완료** — fnrs `reqsupplement` / `submitsupplement`.
**결재** — `PUT /elapr-prgrs-stts/{elaprPrgrsId}/process`
```json
{
"atrzPrgrsSttsComCd": "ATRZ_PRGRS_003",
"atrzOpnnCn": "의견",
"finalApprovalYn": false
}
```
반려: `ATRZ_PRGRS_004`. 기존 `elaprPrgrsSttsService.process`. 최종 시 서버가 `TP`/`RR` 전이.
### 3-5. `TP → RM → RB → RA`
**환경청 1차 회신** — `tech-review` (`TP→RM`).
**광역 지자체회신** — `updatePrgrsStts` `RB`.
**조치완료 (신규)** — `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result`
```json
{
"isSubmit": true,
"actnList": [
{
"rvwOpnnSn": 1,
"actnRsltCn": "수용 — 관로 연장 재산정",
"atchFileGroupId": "optional"
}
],
"totActnSummary": "제출 시 권장",
"muniDocNo": "제출 시 권장"
}
```
- `isSubmit: false` → `RB` 유지 (API만 존재, **화면 버튼 없음**. HWP·첨부는 실무협의서 관리 다이얼로그에서 저장)
- `isSubmit: true` → `RB→RA` (조치완료)
응답: `string` (pwrkDocNo). DTO명 `PwrkActnResultSubmitDto`.
### 3-6. `RA → AV → EV → RK`
**2차 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review`
```json
{
"specialRqstCn": "중점 검증 요청 (선택)"
}
```
`RA→AV`. 응답: `string` (docNo). DTO명 `EnvoActnReviewReqDto`. `specialRqstCn`만 선택.
금액검증 콜백은 서버 → `AV→EV`.
**2차 검토자지정** — `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선(`findDocPrgrsByDocNo`, cycl=1)을 시드로 편집.
**금액검증 재실행** — `POST /dcrag-to/documents/cost/verify`
```json
{
"fnrsCnsltnDocNo": "F-...",
"pwrkCnsltnDocNo": "P-..."
}
```
202. 기존 `requestCostVerification`. `RK` 유지.
### 3-7. `RK → RR | SL`
보완은 3-4와 동일 API. 팝업만 금액 체크.
결재 `process` 2차 최종 → 서버가 `RR`.
### 3-8. `RR → EP → TA`
둘 다 `updatePrgrsStts`. `TA` 확인은 API 없음.
---
## 4. 웹기안기
설계서 기준 **기존**. 구현 시 컨트롤러 존재 여부를 확인한다.
| ID | 용도 | URI | 응답 |
| --- | --- | --- | --- |
| W1 | 웹기안기 Open | `GET .../pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/webhwp/open` | `inline` HWPX blob → `HwpCtrl.Open(File)` |
| W2 | 편집 저장 | `POST .../pwrk-cnsltn-doc-crt-rslt` multipart `file` + `crtDmndUuid` | 201, `verSn+1` |
| W3 | PC 저장 | `GET .../{crtDmndUuid}/download` | `attachment` blob |
버전 지정: `GET .../{crtDmndUuid}/{verSn}/webhwp/open`, `.../{verSn}/download`.
`/{uuid}/latest`, `/{uuid}/download`가 `/{uuid}/{verSn}`보다 우선. path를 섞지 말 것.
다운로드:
- axios `responseType: "blob"`
- 파일명: `Content-Disposition` `filename*=UTF-8''...` (`parseFilenameFromResponse`)
- 서버 파일명: `재원협의 실무협의서({bizNm})_v{verSn}.hwp`
- Content-Type은 `application/x-hwp` 또는 `application/haansofthwpx`일 수 있음. 타입에 의존하지 말 것
Open은 axios `responseType: "blob"`으로 인증된 API를 호출하고, 응답 Blob을 HWPX `File`로 감싸 `HwpCtrl.Open`에 전달한다. `blob:` Object URL은 사용하지 않는다.
multipart `Content-Type` 헤더를 수동 지정하지 않는다 (boundary). JSON `saveJson`은 UI에서 쓰지 않는다.
---
## 5. 전자결재 (`as-elapr-service`)
| API | URI | FE |
| --- | --- | --- |
| 검토자지정·1차 결재요청 | `POST /elapr-dmnd` | O |
| 결재 처리 | `PUT /elapr-prgrs-stts/{id}/process` | O |
| 결재선 복사 | `POST /elapr-apln/copy-line` | **X (서버)** |
`copy-line` body (참고만):
```json
{
"docNo": "P-20260827-0001",
"sourceElaprCycl": 1,
"targetElaprCycl": 2,
"rgtrUuid": "system-uuid"
}
```
`RK`에서 결재현황은 `findDocPrgrsByDocNo`(전체) 조회 후 `elaprCycl`로 1차/2차 탭 표시.
---
## 6. 프론트 호출 금지
| URI | 이유 |
| --- | --- |
| `POST /dcrag/documents/verify-result` | AI→AS 콜백 |
| `POST /dcrag/draft/complete` | AI→AS 콜백 |
| `POST /dcrag/cost/verify-result` | AI→AS 콜백. 여기서 copy-line도 호출 |
| `POST /elapr-apln/copy-line` | 콜백 Feign |
| `POST /pwrk-cnsltn-mng` (`save` 초안 트리거) | v3.0 1차 트리거는 `elapr-dmnd`. 컨트롤러가 남아 있어도 화면에서 쓰지 않음 |
`POST /dcrag-to/documents/draft/generate`는 **Fallback에서만** 허용.
---
## 7. Gap Analysis (프론트 엔티티)
### 이미 있는 것
`fnrsCnsltnMngService`: `findPage`, `findById`, `save`, `saveApply`, `update`, `techReview`, `submitSupplement`, `submitReqSupplement`, `remove`, `changePrgrsSttsComCd`
`dcRagApiToService`: `requestDocumentTransfer`, `requestDraftGeneration`, `getDraftGenerationStatus`, `requestCostVerification`
`elaprDmndService.save`, `elaprPrgrsSttsService.process`
### 추가할 것
| 메서드 | URI |
| --- | --- |
| `requestExamine` | `POST /fnrs-cnsltn-mng/{id}/request-examine` |
| `findFileVrfcResults` | `GET /fnrs-cnsltn-mng/{id}/file-vrfc-results` |
| `requestActnReview` | `POST /fnrs-cnsltn-mng/{id}/request-actn-review` |
| `pwrkCnsltnMngService.*` | 슬라이스 신규 |
| `submitActnResult` | `POST /pwrk-cnsltn-mng/{id}/submit-actn-result` |
| `pwrkCnsltnDocCrtRsltService.*` | 슬라이스 신규 |
`copyLine`은 프론트 서비스에 넣지 않는다.
@@ -0,0 +1,350 @@
# 레퍼런스 — 프론트 엔티티·DTO
> 상위: [개발가이드.md](./개발가이드.md)
> 엔티티 규칙: `frontend/.cursor/skills/backend-controller-to-entity`
---
## 1. ENDPOINT
`apps/as/src/shared/config/api.ts`
```ts
AS_SERVICE: {
// 기존
FNRS_CNSLTN_MNG: "/as-service/api/v1/fnrs-cnsltn-mng",
DCRAG_TO: "/as-service/api/v1/dcrag-to",
// 추가
PWRK_CNSLTN_MNG: "/as-service/api/v1/pwrk-cnsltn-mng",
PWRK_CNSLTN_DOC_CRT_RSLT: "/as-service/api/v1/pwrk-cnsltn-doc-crt-rslt",
}
AS_ELAPR_SERVICE: {
ELAPR_TMPLT: "/as-elapr-service/api/v1/elapr-tmplt",
ELAPR_APLN: "/as-elapr-service/api/v1/elapr-apln",
ELAPR_DMND: "/as-elapr-service/api/v1/elapr-dmnd",
ELAPR_PRGRS_STTS: "/as-elapr-service/api/v1/elapr-prgrs-stts",
}
```
`copy-line`은 `ELAPR_APLN` 하위이나 FE에서 호출하지 않음.
---
## 2. 슬라이스 배치
```
entities/fnrs-cnsltn/ # 확장
entities/dcrag-to/ # 구현됨
entities/elapr/ # 구현됨
entities/pwrk-cnsltn-mng/ # 신규
entities/pwrk-cnsltn-doc-crt-rslt/ # 신규
```
```
entities/pwrk-cnsltn-mng/
├── model/
│ ├── pwrk-cnsltn-mng-interface.ts
│ └── pwrk-cnsltn-mng-payload-schema.ts
├── api/
│ ├── pwrk-cnsltn-mng-service.ts
│ └── queries.ts
└── index.ts
```
`submit-actn-result` Request는 zod SSOT (`PwrkActnResultSubmitDto`).
`request-examine` / `request-actn-review`도 fnrs payload-schema에 추가.
라우트:
| 화면 | 경로 |
| --- | --- |
| 재원협의 목록/상세/작성 | 기존 `app/(main)/(with-base-layout)/fnrs-cnsltn/...` |
| 실무협의 목록 | `.../pwrk-cnsltn/page.tsx` (선택) |
| 실무협의 상세 | `.../pwrk-cnsltn/[id]/page.tsx` (선택) |
| 페이지 UI | `pages/fnrs-cnsltn/`, `pages/pwrk-cnsltn/` |
---
## 3. Service 시그니처
`fnrsCnsltnMngService`에 추가:
```ts
requestExamine(docNo: string, body: FnrsExamineRequestDto): Promise<string>;
findFileVrfcResults(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnFileVrfcRsltResponseDto[]>;
requestActnReview(docNo: string, body: EnvoActnReviewReqDto): Promise<string>;
```
실무협의:
```ts
export interface PwrkCnsltnMngService {
findPage(searchDto: PwrkCnsltnMngSearchDto): Promise<PageResponse<PwrkCnsltnMngResponseDto>>;
findById(pwrkCnsltnDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
findByUpDocNo(upDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
saveApply(body: PwrkCnsltnApplySaveRequestDto): Promise<string>;
submitActnResult(
pwrkCnsltnDocNo: string,
body: PwrkActnResultSubmitDto,
): Promise<string>;
update(pwrkCnsltnDocNo: string, body: PwrkCnsltnMngUpdateRequestDto): Promise<string>;
remove(pwrkCnsltnDocNo: string): Promise<void>;
changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void>;
}
```
v1.0의 `save(): PwrkCnsltnMngSaveResponseDto`(초안 트리거)는 화면에서 사용하지 않는다. 컨트롤러가 남아 있으면 엔티티에 넣더라도 UI 연결 금지.
생성결과:
```ts
export interface PwrkCnsltnDocCrtRsltService {
findPage(
searchDto: PwrkCnsltnDocCrtRsltSearchDto,
): Promise<PageResponse<PwrkCnsltnDocCrtRsltResponseDto>>;
findById(crtDmndUuid: string, verSn: number): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
findLatestByCrtDmndUuid(crtDmndUuid: string): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
openWebHwpLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
download(crtDmndUuid: string, verSn: number): Promise<AxiosResponse<Blob>>;
downloadLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
saveMultipart(formData: FormData): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
updateMultipart(
crtDmndUuid: string,
verSn: number,
formData: FormData,
): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
remove(crtDmndUuid: string, verSn: number): Promise<void>;
}
```
`webhwp/open`은 인증된 axios 요청으로 Blob을 조회한다:
```ts
async function openWebHwpLatest(crtDmndUuid: string) {
return client.get<Blob>(`${BASE}/${crtDmndUuid}/webhwp/open`, {
responseType: "blob",
});
}
```
응답 Blob은 HWPX `File`로 감싸 `HwpCtrl.Open(file, "HWPX", ...)`에 전달한다. `blob:` Object URL은 사용하지 않는다.
DC RAG TO (구현됨, 시그니처 유지):
```ts
requestDocumentTransfer(body: DcRagDocumentTransferRequestDto): Promise<DcRagCommonResponseDto>;
requestDraftGeneration(body: DcRagDraftGenerateRequestDto): Promise<DcRagDraftGenerateResponseDto>;
getDraftGenerationStatus(crtDmndUuid: string): Promise<DcRagDraftStatusResponseDto>;
requestCostVerification(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto>;
```
---
## 4. DTO
타입명 = 백엔드 클래스명. Save/Apply는 zod.
### 4-1. 재원협의 추가
**`FnrsExamineRequestDto`**
| 필드 | 필수 | 값 |
| --- | --- | --- |
| `prgrsSttsComCd` | N | 기본 `FV` |
**`EnvoActnReviewReqDto`**
| 필드 | 필수 |
| --- | --- |
| `specialRqstCn` | N |
**`FnrsCnsltnFileVrfcRsltResponseDto`**: [ref-endpoint.md](./ref-endpoint.md) §3-3.
첨부 검증 배지용으로 `AtflMngClientResponseDto` / `EsntlAtchFileMngTreeResponseDto`에 `vrfcDt`, `vrfcRsltCn`, `vrfcSttsComCd` 확장.
### 4-2. `PwrkActnResultSubmitDto` (zod)
| 필드 | 필수 | 비고 |
| --- | --- | --- |
| `isSubmit` | Y | `false` API만(화면 버튼 없음), `true` 조치완료(`RB→RA`) |
| `actnList` | N | 조치완료 시 선택 |
| `actnList[].rvwOpnnSn` | Y (항목 있을 때) | |
| `actnList[].actnRsltCn` | N | |
| `actnList[].atchFileGroupId` | N | |
| `totActnSummary` | N | 제출 시 권장 |
| `muniDocNo` | N | 제출 시 권장 |
| `pwrkAtchFileGroupId` | N | |
### 4-3. `PwrkCnsltnMng*`
**Search**: `pwrkCnsltnDocNo`, `upDocNo`, `upDocSeComCd`, `prgrsSttsComCd`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `bizNm`
**DetResponse 중첩**
- `cnsltnList`: 협의내역 (`rvwOpnnSn`, `rvwOpnnClsfComCd`, `cnsltnCn`, `rfltYn`, …)
- `pjtcoAjmtList`: 사업비조정
- `crtDmndList[].crtRslt`: 생성요청 + 최신 결과
- `splmntDmndList`: 보완요청
- `sttsChgHstryList`: 상태변경이력 — `SL` 복귀 라벨에 사용
- `trsmFileList`: 전송파일 (Java Det DTO 확인 후 반영)
마스터 조인: `bizNm`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `aplcntNm` 등.
`crtRslt.orgnlOtptDocCn`은 항상 `null`.
**상세 예**
```json
{
"pwrkCnsltnDocNo": "P-20260827-0001",
"upDocNo": "F-20260824-0001",
"upDocSeComCd": "FNRS",
"prgrsSttsComCd": "TE",
"cnsltnList": [],
"pjtcoAjmtList": [],
"crtDmndList": [
{
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
"pwrkCnsltnDocNo": "P-20260827-0001",
"rqstrUuid": "...",
"rqstrNm": "관리자",
"crtSttsComCd": "C",
"crtRslt": {
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
"verSn": 1,
"rgtrNm": "관리자",
"orgnlOtptDocCn": null,
"crtCmptnDt": "2026-08-27 10:47:01"
}
}
],
"splmntDmndList": [],
"sttsChgHstryList": []
}
```
### 4-4. `PwrkCnsltnDocCrtRslt*`
복합 PK: `crtDmndUuid` + `verSn`
**Response**
| 필드 | 타입 | 비고 |
| --- | --- | --- |
| `crtDmndUuid` | `string` | |
| `verSn` | `number` | |
| `rgtrUuid` / `rgtrNm` | `string` | |
| `orgnlOtptDocCn` | `null` | 목록/상세 미포함 |
| `crtCmptnDt` | `string` | |
**Search** (`PageableParams`): `crtDmndUuid`(이력 조회 시 필수), `pwrkCnsltnDocNo`, `verSn`, `rgtrUuid`
```http
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt?page=0&size=10&crtDmndUuid={uuid}&sort=verSn,desc
```
**Save (zod)**: `crtDmndUuid` 필수. `rgtrUuid` 생략 시 서버 로그인 사용자. `file`은 multipart part (DTO 필드 아님).
### 4-5. DC RAG (구현됨)
```ts
interface DcRagDocumentTransferRequestDto {
fnrsCnsltnDocNo: string;
taskSeComCd?: string;
taskSeCdNm?: string;
}
interface DcRagDraftStatusResponseDto {
crtDmndUuid: string;
status: DraftStatus;
statusName?: string;
progressPercent?: number;
events?: EventInfoDto[];
startedDt?: string;
estimatedCompletionDt?: string;
errorMessage?: string | null;
}
type DraftStatus =
| "REQUESTED"
| "DOWNLOADING"
| "ANALYZING"
| "GENERATING"
| "COMPLETED"
| "FAILED";
```
`estimatedMinutes`는 초안 응답에 있을 수 있으나 UX 안내는 **30분 Fallback**을 따른다.
---
## 5. 폴링 훅
초안 (`AC` 또는 `TE`이면서 생성 중):
```ts
useQuery({
queryKey: [...dcragToKeys.draftStatus(crtDmndUuid)],
queryFn: () => dcRagApiToService.getDraftGenerationStatus(crtDmndUuid),
enabled: Boolean(crtDmndUuid) && isPolling,
refetchInterval: (query) => {
const status = query.state.data?.status;
if (status === "COMPLETED" || status === "FAILED") return false;
return 5_000;
},
});
```
`COMPLETED` 시 `fnrs-cnsltn` 상세·`pwrk-cnsltn-mng` `findByUpDocNo` invalidate.
`FV`/`AV`는 `findById`를 5~10초 refetch. `prgrsSttsComCd`가 `ER`/`RK`가 되면 중단. 시작 시각 기준 30분 경과 안내 문구.
`crtDmndUuid` 복원: `findByUpDocNo(fnrsCnsltnDocNo).crtDmndList[0]`.
---
## 6. 다운로드
```ts
const response = await client.get<Blob>(url, { responseType: "blob" });
const filename = parseFilenameFromResponse(response);
const objectUrl = window.URL.createObjectURL(response.data);
```
기존 `attachService`와 동일.
---
## 7. 실무협의 목록 컬럼 (선택 화면)
| 표시명 | Field |
| --- | --- |
| 실무협의번호 | `pwrkCnsltnDocNo` |
| 재원협의번호 | `upDocNo` |
| 진행상태 | `prgrsSttsComCdNm` |
| 사업명 | `bizNm` |
| 시도/구군 | `sido` / `gugun` |
| 신청자 | `aplcntNm` |
| 등록일 | `regDt` |
행 클릭 → `/pwrk-cnsltn/{pwrkCnsltnDocNo}` 또는 재원협의 상세로 이동 (화면설계 확정 후).
---
## 8. `elapr-dmnd` 저장 시 주의
현행 `floating-action-buttons.tsx`:
```ts
elaprCycl: data.detDto?.prgrsSttsComCd === "RK" ? 2 : 1,
docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd, // ER
```
To-Be:
- 검토자지정은 **`ER`(1차)** 과 **`EV`(2차)** 에서.
- `ER`: `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"`, 서식 선택 다이얼로그
- `EV`: `elaprCycl: 2`, `docPrgrsSttsComCd: "RK"`, 1차 결재선 시드 다이얼로그(서식 없음)
`RK` / ATRZ02 FAB: `findDocPrgrsByDocNo` 전체 조회 후 pending은 `elaprCycl === 2`만 사용. 검토완료 후 `ElaprApprovalDialog`에 `elaprCycl={2}`. 보완요청은 TE와 동일 `SplmntDmndDialog`.
@@ -0,0 +1,246 @@
# 레퍼런스 — 프로세스·상태 전이
> 상위: [개발가이드.md](./개발가이드.md)
> SoT: `03.재원협의_실무협의_프로세스_통합_v완결.md` (v3.0, 2026-09-03)
---
## 1. 프로세스 개요
| 항목 | 내용 |
| --- | --- |
| Process No. | PR-AS-003 |
| 메뉴 | 행정지원 > 2.2.2 재원협의 > 2.2.2.1 재원협의 관리 |
| 목적 | 기초 → 광역(경유) → 환경청(승인) → 공단(기술검토/AI) 4자 협의로 사업규모·국비·분담금을 확정 |
2-Pass:
- **Pass 1** 기술 타당성: `SV→ST→LE→FV→ER→AC→TE→TP→RM→RB`
- **Pass 2** 조치결과·금액: `RB→RA→AV→EV→RK→RR→EP→TA`
자체종결(기술검토 생략): `LE→EP→TA`
---
## 2. 4-Lane 시퀀스
```mermaid
sequenceDiagram
autonumber
actor 기초 as 기초 (ROLE_USER05)
actor 광역 as 광역 (ROLE_USER04)
actor 환경청 as 환경청 (ROLE_USER03)
actor 공단 as 공단 (ROLE_USER01)
actor AI as AI 엔진
rect rgb(240, 248, 255)
기초->>기초: [저장] SV
기초->>광역: [신청완료] ST
광역->>환경청: [환경청제출] LE
end
rect rgb(255, 250, 240)
alt 자체종결
환경청->>광역: [결과회신] LE → EP
광역->>기초: [결과회신] EP → TA
else 기술검토 의뢰
환경청->>AI: [기술검토 의뢰] LE → FV
end
end
rect rgb(245, 255, 245)
AI-->>공단: 파일검증 콜백 FV → ER
Note over 공단: 30분 미응답 시 ER + 수동 [파일검증]
alt 파일 미비
공단->>기초: [파일보완] ER → SL
기초->>공단: [파일보완완료] SL → ER
else 적격
공단->>AI: [검토자지정] ER → AC
end
end
rect rgb(255, 245, 245)
AI-->>공단: 초안 콜백 AC → TE
Note over 공단: 30분 미응답 시 TE + 수동 [실무협의서생성]
alt 설계 미비
공단->>기초: [보완요청] TE → SL
기초->>공단: [보완완료] SL → TE
else 검토 완료
공단->>공단: [검토완료] 상신, [결재(승인)] TE 유지
공단->>환경청: [결재(회신)] TE → TP
end
end
rect rgb(250, 240, 255)
환경청->>광역: [재원협의 결과 회신] TP → RM
광역->>기초: [지자체회신] RM → RB
기초->>환경청: [조치완료] RB → RA
end
rect rgb(240, 255, 255)
광역->>환경청: [조치결과서제출] RC → RA
환경청->>AI: [조치결과 검토의뢰] RA → AV
AI-->>공단: 금액검증 콜백 AV → EV
공단->>공단: [검토자지정] EV → RK
Note over 공단: EV에서 1차 결재선 시드로 2차 결재선 수동 배정
end
rect rgb(255, 255, 230)
alt 소명 미비
공단->>기초: [보완요청] RK → SL
기초->>공단: [보완완료] SL → RK
else 확정
공단->>환경청: [결재(회신)] RK → RR
end
end
rect rgb(255, 240, 240)
환경청->>광역: [결과회신] RR → EP
광역->>기초: [결과회신] EP → TA
end
```
거버넌스:
```
기초 (USER05) --ST--> 광역 (USER04) --LE--> 환경청 (USER03)
기초 <-----TA------ 광역 <-----EP/RM------ 환경청
환경청 --FV/ER--> 공단 (USER01) --TP/RR--> 환경청
```
---
## 3. Activity 정의 (14)
| No. | Activity | 주체 | From → To | FE 포인트 |
| --- | --- | --- | --- | --- |
| 1 | 재원협의 조회 | 기초 | — | 기본계획·자가점검 대조 |
| 2 | 등록 및 제출 | 기초·광역 | `SV→ST`, 광역 `LE` | `save-apply`, `updatePrgrsStts` |
| 3 | 접수 및 의뢰 | 환경청 | `LE→FV` / `EP` | 의뢰 vs 자체종결 분기 |
| 4 | AI 파일검증 | 공단·AI | `FV→ER` / `SL` | 30분 Fallback, 파일보완 팝업 |
| 5 | 자체종결 회신 | 환경청·광역 | `LE→EP→TA` | 공단 레인 없음 |
| 6 | 검토자지정·초안 | 공단·AI | `ER→AC→TE` | `elapr-dmnd`, 초안 폴링 |
| 7 | 1차 기술검토 | 공단 결재선 | `TE` / `SL` | 웹기안기, HWP 파싱은 서버 |
| 8 | 기술검토 회신 | 공단 최종 | `TE→TP` | `process` 최종 |
| 9 | 검토결과 회신 | 환경청·광역 | `TP→RM→RB` | 1차 회신. `EP`와 혼동 금지 |
| 10 | 조치결과 작성 | 기초 | `RB→RA` | `submit-actn-result` `isSubmit:true` (조치완료) |
| 11 | 조치 의뢰 | 환경청 | `RA→AV` | `request-actn-review` |
| 12 | AI 금액검증 | AI·공단 | `AV→EV` | 콜백/Fallback 후 접수자 대기 |
| 12a | 2차 검토자지정 | 공단 접수 | `EV→RK` | `elapr-dmnd` cycl=2 |
| 13 | 2차 검토·결재 | 공단 | `RK→RR` / `SL` | 2차 결재선 |
| 14 | 최종 회신 | 환경청·광역·기초 | `RR→EP→TA` | 종결 |
---
## 4. `FNRSSTTS` 상세
관리: `tb_cs_lwpo_cd` / `tb_as_fnrs_cnsltn_mng.prgrs_stts_com_cd`
| 코드 | 국문 | 영문 | 화면 주체 | 전이 |
| --- | --- | --- | --- | --- |
| `SV` | 저장 | Save | 기초 작성 | 신규/`SV` → `SV` |
| `ST` | 신청완료 | Submit to Province | 기초→광역 | `SV→ST` |
| `LE` | 환경청 검토 | Local Environment Review | 광역→환경청 | `ST→LE` |
| `FV` | 파일검증 | File Verification | AI | `LE→FV` |
| `ER` | 기술검토 의뢰 | Examine Request | 공단 접수자 | `FV→ER`, `SL→ER` |
| `SL` | 보완요청 | Supplement Loop | 공단→기초 | `ER\|TE\|RK → SL` |
| `AC` | 초안생성 | AI Auto Creation | AI | `ER→AC` |
| `TE` | 기술검토 | Technical Examine | 공단 1차 결재 | `AC→TE`, `SL→TE` |
| `TP` | 기술검토 회신 | Technical Pass to Basin | 공단→환경청 | `TE→TP` |
| `RM` | 재원협의결과회신 | Result Message to Province | 환경청→광역 | `TP→RM` |
| `RB` | 재원협의결과회신(지자체) | Result Back to Municipality | 광역→기초 | `RM→RB` |
| `RC` | 조치결과서 제출 | Result Complete | 기초→광역 | `RB→RC` |
| `RA` | 조치결과서 제출(환경청) | Result Ask to Basin | 기초→환경청 | `RB→RA` |
| `AV` | 금액검증 | Amount Verification | AI | `RA→AV` |
| `EV` | 조치결과검토의뢰 | Examine Verify | 공단 접수자 | `AV→EV` |
| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `EV→RK`, `SL→RK` |
| `RR` | 조치결과서검토 회신 | Result Return to Basin | 공단→환경청 | `RK→RR` |
| `EP` | 검토결과 회신 | Examine Pass to Province | 환경청→광역 | `LE→EP`(자체), `RR→EP`(최종) |
| `TA` | 검토결과 회신(종료) | Technical Approval | 광역→기초 | `EP→TA` |
### 4-1. v2.1 → v3.0 대조 (프론트 폐기 대상)
| 구간 | v2.1 | v3.0 |
| --- | --- | --- |
| 의뢰 직후 파일검증 | `ER` | **`FV` → `ER`** |
| 검토자지정 후 초안 | `EE` → `DV` | **`ER` → `AC` → `TE`** |
| 1차 내부 결재 | `TR` → `TP` | **`TE` 유지 → `TP`** |
| 환경청→광역 1차 회신 | `EP` | **`RM`** |
| 금액검증 | `RA` | **`RA` → `AV` → `RK`** |
| 공단 2차 회신 | `TP` | **`RR`** |
| 최종 완료 | `TP` / 고시 `TB` | **`TA`** (고시 없음) |
가이드 v1.0 `PWRKSTTS`(`DV`, `AR`, `TR`, `AA` 등)도 동일하게 폐기.
---
## 5. 보완(`SL`) 3루프
복귀 상태는 `tb_as_doc_stts_chg_hstry` 직전 코드. 프론트는 복귀 코드를 요청에 넣지 않고, 라벨만 이력으로 고른다.
```mermaid
stateDiagram-v2
ER --> SL: 파일보완 팝업(파일리스트)
SL --> ER: 파일보완완료
TE --> SL: 보완 검토팝업()
SL --> TE: 보완완료
RK --> SL: 보완 검토팝업(금액)
SL --> RK: 보완완료
```
| 루프 | 팝업 | 요청 | 제출 버튼 |
| --- | --- | --- | --- |
| 파일 무결성 | 파일리스트 + 검증결과 | 공단 접수자 `ER` | [파일보완완료] |
| 1차 기술 | 설계 미달·사유 | 공단 검토자 `TE` | [보완완료] |
| 2차 금액 | 공종별 금액 체크 | 공단 검토자 `RK` | [보완완료] |
---
## 6. AI 3파이프라인·30분 Fallback
| ID | 단계 | 진입 → 완료 | 프론트 진입 | Fallback | 수동 재실행 |
| --- | --- | --- | --- | --- | --- |
| AI-1 | 파일 무결성 | `FV→ER` | 환경청 [기술검토 의뢰] | 30분 → `ER` + [파일검증] | `POST /dcrag-to/documents/transfer` |
| AI-2 | 초안 생성 | `AC→TE` | 공단 [검토자지정] | 30분 → `TE` + [실무협의서생성] | `POST /dcrag-to/documents/draft/generate` |
| AI-3 | 금액 검증 | `AV→EV` | 환경청 [조치결과 검토의뢰] | 30분 → `EV` + 수동 [금액검증] | `POST /dcrag-to/documents/cost/verify` |
- 예상 소요 안내는 **30분** (v1.0의 10분은 폐기).
- 콜백 수신·스케줄러 전이는 서버. FE는 상세 refetch / draft status 폴링만.
- `AC→TE` 성공 시에만 [실무협의서관리]·[검토결과조회] 활성. 실패·타임아웃이면 [실무협의서생성].
초안 폴링 이벤트 예:
| `eventType` | `eventMessage` |
| --- | --- |
| `REQUESTED` | 요청을 접수했습니다. |
| `DOWNLOADING` | 신청서를 수신하는 중입니다. |
| `ANALYZING` | 자료 준비가 완료되었습니다. |
| `GENERATING` | 문서 저장이 완료되었습니다. |
| `COMPLETED` | 문서 생성이 완료되었습니다. |
| `FAILED` | `errorMessage` 표시 |
종료: `status === "COMPLETED" | "FAILED"` 또는 `events`에 해당 `eventType`.
---
## 7. 2차 결재선 (수동 검토자지정)
1. `TE`에서 1차 결재선(`elaprCycl = 1`) 완료.
2. 금액검증 콜백/Fallback → `AV→EV`.
3. 공단 접수자가 `EV`에서 [검토자지정]: 1차 결재선을 시드로 편집 후 `POST /elapr-dmnd` (`elaprCycl: 2`) → `RK`.
4. `copy-line` 자동 복사는 사용하지 않는다.
결재 `process`:
- 상신·중간 승인: 문서 상태 `TE` 또는 `RK` 유지.
- 1차 최종: 서버가 `TE→TP`.
- 2차 최종: 서버가 `RK→RR`.
- 반려: 직전 결재자 복귀. 문서 상태 유지.
---
## 8. 웹기안기
- `TE`/`RK` [실무협의서관리] → `webhwp/open` Blob 조회 후 팝업에서 `HwpCtrl.Open(File)`.
- [검토완료] 시 HWP 파싱·DB 등재는 **서버**. FE는 결재 `process`만.
- 편집본 저장: `POST /pwrk-cnsltn-doc-crt-rslt` multipart. `verSn`은 클라이언트가 보내지 않음.
@@ -0,0 +1,181 @@
# 레퍼런스 — 화면·버튼 매트릭스
> 상위: [개발가이드.md](./개발가이드.md)
> SoT: `03` §4 화면 및 버튼별 상세 상태전이 명세서 (Row 4~42, 38행)
구현 위치: `pages/fnrs-cnsltn/lib/button-config.ts` + `ui/components/floating-action-buttons.tsx`
---
## 1. 화면 구분
| 화면 | 소유 | 라우트 (현행) | 비고 |
| --- | --- | --- | --- |
| 작성화면 | 기초 `ROLE_USER05` | `/fnrs-cnsltn/new`, `/fnrs-cnsltn/{id}/edit` | `SV` |
| 수정화면 | 기초 | `/fnrs-cnsltn/{id}/edit` | `SL` 보완 |
| 상세화면 | 광역·환경청·공단·기초(`RB`/`TA`) | `/fnrs-cnsltn/{id}` | 조회 + 액션 버튼 |
| 자동화 | AI | 버튼 없음 | `FV`/`AC`/`AV` 진행 패널 |
공단 상세는 결재선 구분으로 같은 상태라도 버튼이 다르다.
| `atrzSeComCd` | 역할 |
| --- | --- |
| `ATRZ01` | 접수자 |
| `ATRZ02` | 검토자 |
| `ATRZ03` | 승인자 |
| `ATRZ99` | 최종승인자 |
---
## 2. 38행 매트릭스
API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일.
| 순번 | 화면 | 권한 | 결재선 | 상태 | 버튼 | 전이 | 다음 담당 | FE 동작 |
| :---: | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | 작성 | USER05 | - | 빈값, `SV` | 저장 | `SV` | 본인 | `save-apply` `SV` |
| 2 | 작성 | USER05 | - | `SV` | 신청완료 | `ST` | 광역 | `save-apply` `ST` |
| 3 | 작성 | USER05 | - | `SV` | 삭제 | 삭제 | — | `DELETE` |
| 4 | 상세 | USER04 | - | `ST` | 환경청제출 | `LE` | 환경청 | `updatePrgrsStts` |
| 5 | 상세 | USER03 | - | `LE` | 결과회신 | `EP` | 광역 | `tech-review` 자체종결 |
| 6 | 상세 | USER03 | - | `LE` | 기술검토 의뢰 | `FV` | 공단 접수 | **`request-examine`** |
| 7 | 자동화 | AI | - | `FV` | (파일검증) | `ER` | 공단 접수 | FE 호출 없음. 진행 패널 |
| 8 | 상세 | USER01 | 접수자 | `ER` | 파일보완 | `SL` | 기초 | 팝업(파일리스트) → `reqsupplement` |
| 9 | 상세 | USER01 | 접수자 | `ER` | 파일검증 | `ER` | 본인 | `dcrag-to/transfer` 재실행 |
| 10 | 상세 | USER01 | 접수자 | `ER` | 검토자지정 | `AC` | 다음 결재자 | `POST /elapr-dmnd` |
| 11 | 자동화 | AI | - | `AC` | (초안생성) | `TE` | 공단 검토 | FE 호출 없음. 폴링 |
| 12 | 상세 | USER01 | 검토자 | `TE` | 실무협의서관리 | 유지 | — | 웹기안기 Open. **초안 성공 시만** |
| 13 | 상세 | USER01 | 검토자 | `TE` | 검토결과조회 | 유지 | — | 실무협의 상세 |
| 14 | 상세 | USER01 | 검토자 | `TE` | 실무협의서생성 | 유지 | — | **실패/타임아웃/초안 없음** 시 |
| 15 | 상세 | USER01 | 검토자 | `TE` | 저장 | `TE` | — | `pwrk save-apply` |
| 16 | 상세 | USER01 | 검토자 | `TE` | 검토완료 | `TE` | 다음 결재자 | `elapr process` 상신. 반려 시 직전 결재자 |
| 17 | 상세 | USER01 | 검토자 | `TE` | 보완요청 | `SL` | 기초 | 보완 팝업() → `reqsupplement` |
| 18 | 상세 | USER01 | 승인자 | `TE` | 결재(승인) | `TE` | 다음 결재자 | `process` |
| 19 | 상세 | USER01 | 최종승인자 | `TE` | 결재(회신) | `TP` | 환경청 | `process` 최종 |
| 20 | 수정 | USER05 | - | `SL` | 파일보완완료 | `ER` | 공단 접수 | `submitsupplement` (직전=`ER`) |
| 21 | 수정 | USER05 | - | `SL` | 보완완료 | `TE` | 공단 검토 | `submitsupplement` (직전=`TE`) |
| 22 | 수정 | USER05 | - | `SL` | 보완완료 | `RK` | 공단 검토 | `submitsupplement` (직전=`RK`) |
| 23 | 상세 | USER03 | - | `TP` | 재원협의 결과 회신 | `RM` | 광역 | `tech-review` |
| 24 | 상세 | USER04 | - | `RM` | 지자체회신 | `RB` | 기초 | `updatePrgrsStts` |
| 25 | 상세 | USER05 | - | `RB` | 실무협의서 관리 | 유지 | — | 웹기안기 Open |
| 26 | 상세 | USER05 | - | `RB` | 조치완료 | `RA` | 환경청 | **`submit-actn-result` `isSubmit:true`** |
| 27 | 상세 | USER04 | - | `RC` | 조치결과서제출 | `RA` | 환경청 | `updatePrgrsStts` |
| 28 | 상세 | USER03 | - | `RA` | 실무협의서 관리 | 유지 | — | 웹기안기 Open. **저장 불가** |
| 28a | 상세 | USER03 | - | `RA` | 조치결과 검토의뢰 | `AV` | 공단 검토 | **`request-actn-review`** |
| 29 | 자동화 | AI | - | `AV` | (금액검증) | `EV` | 공단 접수 | FE 호출 없음. 진행 패널 |
| 29a | 상세 | USER01 | 접수자 | `EV` | 검토자지정 | `RK` | 공단 검토 | `POST /elapr-dmnd` cycl=2. 1차 결재선 시드 |
| 30 | 상세 | USER01 | 검토자 | `RK` | 실무협의서관리 | 유지 | — | 웹기안기 |
| 31 | 상세 | USER01 | 검토자 | `RK` | 금액검증 | `RK` | — | `dcrag-to/cost/verify` |
| 32 | 상세 | USER01 | 검토자 | `RK` | 저장 | `RK` | — | `pwrk save-apply` |
| 33 | 상세 | USER01 | 검토자 | `RK` | 검토완료 | `RK` | 다음 결재자 | `process` |
| 34 | 상세 | USER01 | 승인자 | `RK` | 결재(승인) | `RK` | 다음 결재자 | `process` |
| 35 | 상세 | USER01 | 최종승인자 | `RK` | 결재(회신) | `RR` | 환경청 | `process` 최종 |
| 36 | 상세 | USER03 | - | `RR` | 실무협의서 관리(조회), 검토결과 회신 | `EP` | 광역 | `tech-review` |
| 37 | 작성/상세 | USER04 | - | `EP` | 결과회신, 실무협의서 관리(조회) | `TA` | 기초 | `updatePrgrsStts` |
| 38 | 상세 | USER05 | - | `TA` | 결과회신, 실무협의서 관리(조회) | — | — | **API 없음. 조회** |
`SL` 20~22행: 버튼은 직전 상태에 따라 **하나만** 노출. 파일 루프면 [파일보완완료], 그 외 [보완완료].
---
## 3. `button-config` 권장 구조
액션 키는 현행 `FNRS_CNSLTN_VIEW_BUTTON_ACTION`을 확장한다. 신규·이름 변경:
| 신규/변경 액션 | 라벨 | 비고 |
| --- | --- | --- |
| `fileSplmnt` | 파일보완 | `ER` 접수자. 현행에 없음 |
| `fileVrfc` | 파일검증 | 유지. `ER` 재실행 |
| `dsgntRvwr` | 검토자지정 | `ER→AC`(1차), `EV→RK`(2차) |
| `pwrkCnsltnDocMng` | 실무협의서관리 | 유지. `TE`·`RK`·`RB`·`RA`(조회 전용) |
| `pwrkCnsltnDocCrt` | 실무협의서생성 | 유지. 실패/타임아웃만 |
| `rvwRsltInq` | 검토결과조회 | 신규 |
| `actnCmptn` | 조치완료 | `RB→RA`. `isSubmit:true` |
| `actnSbmsn` | 조치결과서제출 | 광역 `RC→RA` |
| `rqstActnRvw` | 조치결과 검토의뢰 | 환경청 `RA→AV` |
| `costVrfc` | 금액검증 | `RK` |
| `lclRply` | 지자체회신 | 광역 `RM→RB` |
| `fnrsRsltRply` | 재원협의 결과 회신 | 환경청 `TP→RM` |
| `rsltRply` | 결과회신 | `LE→EP`, `RR→EP`, `EP→TA`, `TA` 조회 |
| `fileSplmntCmptn` | 파일보완완료 | `SL←ER` |
| `splmntCmptn` | 보완완료 | `SL←TE\|RK` |
**삭제**: `fnrsCnsltnAncmnt`(고시→`TB`), `sncnAprv`(인가승인). `EP`/`TA`는 `rsltRply`로 통일.
### 3-1. 역할 × 상태 (공단 제외)
```
ROLE_USER05
"" | SV → 저장, 신청완료, 삭제
SL → (직전 ER) 파일보완완료 | (직전 TE|RK) 보완완료
RB → 실무협의서 관리, 조치완료
TA → 결과회신 (조회), 실무협의서 관리(조회)
ROLE_USER04
ST → 환경청제출 // 현행 반려(rjct→SV)는 SoT 38행에 없음. 유지 시 별도 합의
RM → 지자체회신
RC → 조치결과서제출
EP → 결과회신, 실무협의서 관리(조회)
ROLE_USER03
LE → 결과회신(자체종결), 기술검토 의뢰
TP → 재원협의 결과 회신
RA → 실무협의서 관리(조회), 조치결과 검토의뢰
RR → 실무협의서 관리(조회), 검토결과 회신 (`tech-review` → `EP`)
```
### 3-2. 공단 결재선 × 상태
```
ER / ATRZ01 접수자
→ 파일보완, 파일검증, 검토자지정
EV / ATRZ01 접수자
→ 검토자지정 (2차 결재선. 1차 결재선 시드, 서식 선택 없음)
TE / ATRZ02 검토자
→ (초안 성공) 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청
→ (초안 실패·타임아웃) 실무협의서생성 (+ 위 항목 중 관리/조회는 초안 생긴 뒤)
TE / ATRZ03 승인자 → 결재(승인)
TE / ATRZ99 최종 → 결재(회신)
RK / ATRZ02 검토자
→ (초안 완료) 실무협의서관리
→ (초안 완료 + 결재 대기 ATRZ_PRGRS_002, cycl2) 보완요청, 검토완료
· 보완요청 UI: TE와 동일 SplmntDmndDialog
RK / ATRZ03 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(승인)
RK / ATRZ99 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(회신)
· 결재 UI: ElaprApprovalDialog (elaprCycl=2) 승인/반려 → PUT process
```
`FV`, `AC`, `AV`: 액션 빈 배열 + 진행 패널.
`PENDING_APPROVAL_ACTIONS`: `dmndSplmnt`, `rvwCmptn`, `atrzAprv`, `atrzRply`만.
`pwrkCnsltnDocMng`는 결재 대기와 무관(초안 완료만). ATRZ02·ATRZ03·ATRZ99라도 pending 없으면 관리만 노출.
---
## 4. 초안 성공 vs 실패 버튼
`TE`에서 `crtDmndList[].crtSttsComCd` / 폴링 `status`로 분기.
| 조건 | 노출 |
| --- | --- |
| `C` / `COMPLETED` | 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청 |
| `F` / `FAILED` 또는 30분 Fallback 후 초안 없음 | 실무협의서생성 |
| `R`/`I` 생성 중 | 진행 패널. 위 버튼 비활성 (`AC`와 동일 UX, 상태가 이미 `TE`면 패널+생성 버튼) |
`RK`는 금액검증 성공이 기본. Fallback 후 [금액검증] 재실행만 추가 노출.
---
## 5. 현행 `button-config.ts`와의 차이
| 항목 | 현재 | To-Be |
| --- | --- | --- |
| USER05 | `""/SV/TA(고시)` | `SL`, `RB` 추가. `TA`는 조회 |
| USER04 | `ST`(제출+반려), `EP`(인가승인) | `RM/RC/EP` 추가. 인가승인 삭제 |
| USER03 | `LE`, `TP`(→EP로 회신) | `RA`, `RR` 추가. `TP`는 `RM` |
| USER01 | `ER`(파일검증, 검토자지정), `TE` | `ER`에 파일보완. `RK` 전면 추가 |
| USER01 ATRZ | `ER`, `TE`만 `needsRoleUser01AtrzResolve` | `RK` 포함 |
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,674 @@
# dmbpRegist.jsp → React 마이그레이션 분석·구현 가이드
| 항목 | 내용 |
| ----------- | ------------------------------------------------------ |
| 원본 JSP | `adssNewOld/.../fnrsc/dmbp/dmbpRegist.jsp` |
| 대상 앱 | `frontend/apps/as` |
| 도메인 | 하수도기본계획 (구: 기본계획검토 / dmbp) |
| API 엔티티 | `@/entities/sewer-bsc-plan` (`SewerBscPlanController`) |
| 참조 패턴 | `@/pages/fnrs-cnsltn` + `@/widgets/pbadms-sprt` |
| 문서 목적 | contents 영역 **섹션별** UI·상태·API 매핑 및 구현 방향 |
| 작성 기준일 | 2026-07-27 |
---
## 1. 마이그레이션 범위
### 1-1. 포함 (contents만)
JSP `568~1274`행: `<div class="contents">` ~ `<!-- //contents -->`
| 영역 | JSP 식별 | 개요 |
| ---- | ---------------------------------- | --------------------------------------------------------------- |
| A | `gTitle1` | 페이지 타이틀·브레드크럼 |
| B | `slide_10` | 기본계획변경 신청(마스터·연도·개요) |
| C | `slide_15` | 신청자·설계자 정보 |
| D | `slide_19` | 개인정보 동의 |
| E | `slide_11` | 사업개요(단계별 인구·보급률) |
| F | `slide_12` | 총사업비 — `BizCstMatrixFormSection` 전용 |
| G | `slide_13` | 재원분담 |
| H | `slide_14` | 처리시설별 하수도정비기본계획 (Checkbox + Tabs) |
| I | `slide_16` | 체크리스트 + 필수첨부 + 기타첨부 |
| J | JSP 상단 `saveBtn` / `presentnBtn` | React: 폼 하단 액션바 (`fnrs-form-page` 279~323과 동일 구성, API만 sewer) |
### 1-2. 제외
- `<%@ include %>`: top / sideMenu / footer / `bscPlnPopup.jsp` / `sndrdBzAmnt.jsp`
- 레이아웃·메뉴·권한 셸 → 기존 `BaseLayout` / `PageLayout` / AUTH 체계
- `mStep1` 진행 단계 표시 (미구현 · 마이그레이션 범위 밖)
팝업(`이전기본계획조회`)은 **별도 Dialog 컴포넌트**로 재구현하며, include JSP 자체는 포팅하지 않는다.
---
## 2. 목표 아키텍처
```
app/(with-base-layout)/sewer-bsc-plan/regist/page.tsx
└─ pages/sewer-bsc-plan/ui/sewer-bsc-plan-regist-page.tsx
└─ sewer-bsc-plan-form-page.tsx # FormProvider + 섹션 조립
├─ sections/*-form-section.tsx # 도메인 전용
└─ widgets/pbadms-sprt/* # 공통 재사용
entities/sewer-bsc-plan/ # 이미 구현됨
saveApply / findDetails / submitSupplement …
```
**폼 스택 (fnrs-cnsltn과 동일)**
- `react-hook-form` + `zod` (`zodResolver`, `mode: "onChange"`)
- 섹션 래퍼: `SectionCollapsible` (Accordion 아님)
- UI: `@common/components` (Input, Select, Textarea, Button, Radio, Tabs, Table 등)
- 그리드가 필요한 곳만 `@tanstack/react-table` + `DataTable`
- 데이터: React Query (`useSaveApplySewerBscPlan` 등) — 목데이터는 API 미연동 구간만 `@faker-js/faker`
**Vercel React Best Practices (적용 요약)**
- `client-swr-dedup`: React Query로 체크리스트/필수첨부/코드 조회 중복 제거
- `bundle-dynamic-imports`: 처리시설 Tabs 패널·이전계획 Dialog는 `next/dynamic` 후보
- `rerender-derived-state`: 단계 연도·합계·진행률은 render 중 파생, 불필요 effect 금지
- `async-parallel`: 등록 진입 시 코드/체크리스트트리/필수첨부트리를 병렬 조회
---
## 3. 전체 레이아웃
`fnrs-form-page.tsx`와 동일한 **FormProvider + 세로 섹션 스택** 구조를 따르고,
하단 액션바는 **[`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx)** 과 동일 구성(목록·저장·신청완료·삭제·보완완료)이며 **API/라우트만 sewer** 로 바꾼다.
타이틀·브레드크럼은 폼 밖(`PageLayout` / regist·edit·view 셸)에서 처리한다.
(상세 View의 sticky FAB·검토/결재 버튼은 본 등록 문서 범위 밖 — `fnrs-view-page` 대응 후속.)
### 3-1. 컴포넌트 트리 (fnrs 대응)
```
PageLayout (title / breadcrumb)
└─ SewerBscPlanRegistPage | SewerBscPlanEditPage | SewerBscPlanViewPage
└─ SewerBscPlanFormPage | SewerBscPlanViewPage 본문
FormProvider (등록/수정) / 읽기 섹션 (상세)
└─ <form|div className="mt-6 space-y-4">
├─ BasicInfoFormSection // JSP slide_10
├─ ContactInfoFormSection // JSP slide_15 (pbadms-sprt)
├─ PrivacyConsentSection // JSP slide_19 (pbadms-sprt, 등록·수정)
├─ OutlineFormSection // JSP slide_11 (사업개요, 신규)
├─ BizCstMatrixFormSection // JSP slide_12 (총사업비 전용, 신규)
├─ FnrsCstFormSection // JSP slide_13 (재원분담, 신규 ※ FnrsShar 아님)
├─ TmplntFormSection // JSP slide_14 (처리시설, Tabs로 시설별 패널)
├─ ChecklistFormSection // JSP slide_16 (pbadms-sprt)
├─ AttachmentsFormSection // JSP 필수·기타첨부 (pbadms-sprt)
├─ SplmntDmndFormSection // edit + SL 상태만 (pbadms-sprt)
└─ form footer action bar // fnrs-form-page 279~323 동일
// [목록] / [저장] / edit시 [신청완료][삭제] / SL시 [보완완료]
```
**폼 하단 액션바 버튼 구성**
기준: [`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx). UI·분기만 동일하고 훅/라우트는 sewer.
| 순서 | 버튼 | 조건 | sewer 비고 |
| ---- | -------- | ----------------------------------------- | ---------------------------------------------------- |
| 1 | 목록 | 항상 | `router.push("/sewer-bsc-plan")` |
| 2 | 저장 | `!isSplmntPrgrsStts` | `type="submit"` → `saveApply` / 임시저장(SV) |
| 3 | 신청완료 | `!isSplmntPrgrsStts && isEditMode` | 체크리스트+필수첨부 검증 → `saveApply` / LE |
| 4 | 삭제 | `!isSplmntPrgrsStts && isEditMode` | confirm → `delete` → 목록 |
| 5 | 보완완료 | `isSplmntPrgrsStts` (`prgrsSttsComCd=SL`) | `submitSupplement` (fnrs `onSplmntCmptn` 대응) |
**fnrs와의 차이 (섹션 치환만, 골격 동일)**
| fnrs-form-page | sewer-bsc-plan |
| ---------------------------------------------- | ------------------------------------------------------ |
| `BasicInfoFormSection` | 동일 역할 (마스터·연도·개요 필드는 sewer DTO) |
| `ContactInfoFormSection` | 동일 (`fieldPrefix="sewerBscPlanDtl"`) |
| `PrivacyConsentSection` | 동일 |
| `BusinessScaleFormSection` | → `OutlineFormSection` (단계별 인구·보급률) |
| `BusinessCostFormSection` | → `BizCstMatrixFormSection` (단계×원가 계층 전용) |
| `FnrsSharFormSection` | → `FnrsCstFormSection` (국고·지방비·원인자·기타) |
| _(없음)_ | `TmplntFormSection` (체크박스 + Tabs 시설별 매트릭스) |
| `ChecklistFormSection` `docSeComCd="FNRS"` | `docSeComCd="SEWER"` (TBD) |
| `AttachmentsFormSection` | 동일 패턴 |
| `SplmntDmndFormSection` | 동일 (`readOnly={!isSplmntPrgrsStts}`) |
| 폼 하단 액션바 (목록·저장·신청완료·삭제·보완완료) | **동일 구성, API·라우트만 sewer** |
### 3-2. 본문 ASCII (폼 페이지)
```
┌─────────────────────────────────────────────────────────────────┐
│ PageLayout: 하수도기본계획 신청/수정 breadcrumb │
├─────────────────────────────────────────────────────────────────┤
│ FormProvider │
│ <form className="mt-6 space-y-4"> │
│ │
│ ┌─ SectionCollapsible: 기본계획 신청 ─────────────────────┐ │
│ │ BasicInfoFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 신청자 정보 ────────────────────────────────────────────┐ │
│ │ ContactInfoFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 개인정보 동의 ──────────────────────────────────────────┐ │
│ │ PrivacyConsentSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 사업개요 ────────────── [이전기본계획조회] ─────────────┐ │
│ │ OutlineFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 총사업비 (백만원) ──────────────────────────────────────┐ │
│ │ BizCstMatrixFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 재원분담 (백만원) ──────────────────────────────────────┐ │
│ │ FnrsCstFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 처리시설별 하수도정비기본계획 ──────────────────────────┐ │
│ │ TmplntFormSection │ │
│ │ ├─ BscFctList Checkbox │ │
│ │ └─ Tabs (선택 시설별 TabsTrigger / TabsContent) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 체크리스트 ─────────────────────────────────────────────┐ │
│ │ ChecklistFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 첨부파일 ───────────────────────────────────────────────┐ │
│ │ AttachmentsFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─ 보완요구 (edit + SL) ───────────────────────────────────┐ │
│ │ SplmntDmndFormSection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ form footer (fnrs-form-page 279~323 동일) ──────────────┐ │
│ │ [목록] [저장] / edit: [신청완료][삭제] / SL: [보완완료] │ │
│ │ ※ API·라우트만 sewer — UI 분기는 fnrs와 동일 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ </form> │
└─────────────────────────────────────────────────────────────────┘
```
### 3-3. 액션바 동작 (`fnrs-form-page` 279~323 동일, API만 sewer)
구현 위치: `sewer-bsc-plan-form-page.tsx` 하단
(`fnrs-form-page.tsx`의 `mx-4 flex flex-wrap justify-center gap-3` 블록을 그대로 옮기고 훅·path만 교체)
| 버튼 | 조건 | 대표 동작 |
| -------- | ---------------------------- | -------------------------------------------------------------------------- |
| 목록 | 항상 | `router.push("/sewer-bsc-plan")` |
| 저장 | `!isSplmntPrgrsStts` | `type="submit"` → `saveApply` + 임시저장(`SV`) |
| 신청완료 | edit && `!isSplmntPrgrsStts` | 체크리스트 완료 + **필수 첨부 완료** 검증 → `saveApply` + `LE` |
| 삭제 | edit && `!isSplmntPrgrsStts` | confirm → `delete` → 목록 |
| 보완완료 | `isSplmntPrgrsStts` (SL) | `form.handleSubmit` → `useSubmitSupplementSewerBscPlan` |
**신청완료 사전 검증** (fnrs `handleApplyClick`과 동일 정책)
1. **체크리스트**: `isFormChecklistComplete(cklstResultList)` — 미완료 시 `"모든 체크리스트 항목을 체크해야 신청이 가능합니다."`
2. **필수 첨부**: `esntlAtchYn === "Y"`인 필수첨부 슬롯마다 `esntlAtchFileList`에 `atchFileGroupId`가 있어야 함
- Form: `AttachmentsFormSection`의 트리 + 폼 `esntlAtchFileList`
- 미충족 시 예: `"필수 첨부파일을 모두 등록해야 신청이 가능합니다."`
- 헬퍼 후보: `isFormEsntlAtchComplete(requiredTree, esntlAtchFileList)` (`pbadms-sprt`에 추가)
저장에는 위 검증을 적용하지 않는다 (임시저장 허용).
저장/제출 버튼을 섹션 헤더(`gTitle2 gRt`)에 두지 않는다 — **폼 하단 액션바 단일 진입점** (§4-J).
### 3-4. 라우트
| 화면 | path | 셸 → FormPage |
| ---- | --------------------------- | ------------------------------------------ |
| 등록 | `/sewer-bsc-plan/regist` | `mode="create"` |
| 수정 | `/sewer-bsc-plan/[id]/edit` | `mode="edit"` + `useSewerBscPlanDetails` |
| 상세 | `/sewer-bsc-plan/[id]` | ViewPage (본 문서 범위 밖, fnrs-view 대응) |
`docSeComCd` 후보: 백엔드 체크리스트·필수첨부 등록값 확인 후 **`SEWER`** (또는 레거시 `DMBP`) — **TBD, CMS 코드와 맞출 것**.
---
## 4. 섹션별 상세 구현
### 4-0. Hidden / 세션 컨텍스트 (폼 루트)
JSP hidden → React 폼 기본값·세션 스토어로 흡수.
| JSP | 의미 | React |
| ------------------------------- | --------------- | ------------------------------------------------------- |
| `chgSe` / `chgSeSp` / `chgSeCd` | 변경구분 | `sewerBscPlan.sewerBscPlanSeComCd` + 표시용 코드명 |
| `progrsSttus` | SAVE / PRESENTN | 저장·제출 시 `prgrsSttsComCd`로 매핑 (임시저장 vs 제출) |
| `planSeCd` | 고정 `A` | 필요 시 상수 또는 API 필드 확인 |
| `adstDstrCd` / `rgnCd` | 행정구역 | `sewerBscPlan.admdstCd` |
| `sidoNm` / `gugunNm` | 표시용 | 읽기 전용 표시 (`detDto.sido`/`gugun`) |
구현: `getCreateDefaultValues(userInfo)`에서 로그인 사용자·소속 지자체 주입.
---
### 4-A. 타이틀·브레드크럼
| JSP | React |
| ------------------------ | ------------------------------------------------------ |
| `h3` 기본계획검토 | PageLayout title: **하수도기본계획 신청** (IIPBS 명명) |
| 설치인가 및 제원협의 > … | 메뉴 메타 기반 breadcrumb |
별도 섹션 컴포넌트 불필요. 라우트 `page.tsx` / layout metadata로 처리.
---
### 4-B. 기본계획변경 신청 (`slide_10`)
**파일**: `ui/sections/basic-info-form-section.tsx`
**래퍼**: `SectionCollapsible` title=`기본계획 신청`
**필드 접두사**: `sewerBscPlan.*` + `sewerBscPlanDtl.*`
| 표시명 | JSP | API 매핑 | UI | 필수 | 비고 |
| ---------------- | ----------------- | ------------------------------- | ------------------ | ---- | ---------------------------------- |
| 기본계획명 | `drngMtncPlanNm` | `sewerBscPlan.sewerBscPlanNm` | Input | Y | |
| 계획년도 | `planYr` | `sewerBscPlanDtl.planYr` | Select (현재~2005) | Y | |
| 기준년도 | `crtrYr` | `sewerBscPlanDtl.crtrYr` | Select | Y | |
| 1~4단계 목표연도 | `goalYrStep1~4` | `frlvlGoalYr`~`folvlGoalYr` | Select (현재~+30) | Y | 1단계 변경 시 +5/+10/+15 자동 연동 |
| 계획개요 | `bzCn` | `sewerBscPlanDtl.bizOtlnCn` | Textarea | Y | |
| 구분 | `#chgSeNm` | `sewerBscPlanSeComCd` (+코드명) | 읽기 또는 Select | | 신규/변경 등 |
| 신청 지자체 | `${lclgvrnmntNm}` | `admdstCd` | 읽기 전용 | | |
**동작**
- `goalYrStep1` onChange → 2·3·4단계 기본값 세팅 + E/F/G 헤더 연도 span 갱신 (`watch` 파생)
- JSP 상단 `gRt` 저장/제출은 사용하지 않음 → FormPage 하단 액션바 (§3-3, §4-J)
---
### 4-C. 신청자 정보 (`slide_15`)
**재사용**: `@/widgets/pbadms-sprt`의 `ContactInfoFormSection`
**props**: `fieldPrefix="sewerBscPlanDtl"` (또는 dtl 필드 구조에 맞게)
| 표시 | JSP | API |
| ---------------------------- | --------------------------- | --------------------------------------------------------------------------------------- |
| 신청자 이름/전화/부서/이메일 | `${UsrNm}` 등 서버렌더 | 로그인 사용자 읽기 전용 → `aplcntNm`, `aplcntMblTelno`, `aplcntOgdpNm`, `aplcntEmlAddr` |
| 설계자 | `dsgnrNm/Telno/Psitn/Email` | `dsgnrNm`, `dsgnrTelno`, `dsgnrOgdpNm`, `dsgnrEmlAddr` |
신청자 4필드는 세션에서 defaultValues에 채우고 Input `disabled`.
---
### 4-D. 개인정보 동의 (`slide_19`)
**재사용**: `PrivacyConsentSection`
| JSP | React |
| ----------------------------- | ------------------------------------------------------------------ |
| 고지 textarea + 보유기간 문구 | 동일 문구 상수/마크다운 |
| `privacyAgree` Y/N | 폼 필드 `privacyAgree: "Y" \| "N"` (API 미전송, 클라이언트 검증만) |
**검증**: 제출 시 `Y` 필수 (JSP `presentnBtn`과 동일). 저장(임시)은 동의 없이 허용할지 정책 TBD — 기본은 JSP와 같이 **제출만 강제**.
---
### 4-E. 사업개요 (`slide_11`)
**파일**: `ui/sections/outline-form-section.tsx` (신규, 도메인 전용)
**API**: `sewerBscPlanOtlnList: SewerBscPlanOtlnItem[]` (planStpNo 1~4)
| 행(구분) | JSP name | Item 필드 |
| ------------ | --------------- | ------------------- |
| 계획인구 | `arrPlanPpltn` | `sewerPlanPpltnCnt` |
| 처리인구 | `arrPrcsPpltn` | `sewerPrcsPpltnCnt` |
| 하수도보급율 | `arrDrngSplyRt` | `sewerPsplyRt` |
열: 1~4단계 (헤더에 `frlvlGoalYr` 등 표시).
**버튼**: `이전기본계획조회` → Dialog (`PreviousPlanLookupDialog`)
- 레거시: `fnbscPlnPopupToggle` + `bscPlnPopup.jsp`
- React: Modal + 조회 API (**TBD**: 이전 계획 조회 엔드포인트; 없으면 1차는 Dialog UI만 + mock)
숫자 입력: 콤마 제거 후 number 전송 (제출 시 JSP `replaceComma` 대체).
---
### 4-F. 총사업비 (`slide_12`)
**파일**: `ui/sections/biz-cst-matrix-form-section.tsx` (**전용 컴포넌트** — fnrs `BusinessCostFormSection` 재사용 금지)
**래퍼**: `SectionCollapsible` title=`총사업비` (단위 `백만원`은 헤더 우측)
**JSP 현실**: 단계(1~4) × 원가 계층(총사업비, 공사비, 처리시설, 하수관로 신설/보수, 펌프장, 배수설비, 기타, 부대경비, 설계/감리/부대비). 일부 readonly·자동합산 JS.
**신규 API**: `bizCstList: SewerBscPlanBizCstItem[]` = `{ planStpNo, acntgSeCd, cstrnAmt }`
**구현 방향 (`BizCstMatrixFormSection` 전용)**
- 행 = `useAcntgSeMngTree` → `buildAcntgCostItems` (fnrs `BusinessCostFormSection`과 동일 출처)
- `costItems` 로드 시 `useEffect`로 `bizCstList`를 `mergeBizCstList(existing, costItems)`로 동기화
- 열 = `planStpNo` 1~4 (헤더에 목표연도 표시)
- readonly 행(`CostItemDef.readonly=true`)은 children 합산으로 자동계산; Input에 `readOnly` + `bg-muted`
- 변경 시 `setAmt` → `recalculateBizCstMatrix(list, costItems)` → `setValue("bizCstList")`
- 저장 시 섹션 onChange 합산을 신뢰하여 `bizCstList` 그대로 전송 (`toBizCstSaveList` identity)
**표준사업비 조회** 버튼은 JSP에서 주석 처리됨 → **미구현**.
---
### 4-G. 재원분담 (`slide_13`)
**주의**: fnrs의 `FnrsSharFormSection`(재원협의 공유구분 트리)과 **다름**.
| 행 | JSP | `SewerBscPlanFnrsCstItem` |
| ------------ | -------------- | ------------------------- |
| 총사업비 | `arrTotAmnt` | `sumAmt` (또는 파생) |
| 국고 | `arrNlepAmt` | `gvexpAmt` |
| 지방비 | `arrLcepAmt` | `lcexpAmt` |
| 원인자부담금 | `arrCusrAlotm` | `orgprBdchgAmt` |
| 기타 | `arrEtcAmt` | `etcAmt` |
**파일**: `ui/sections/fnrs-cst-form-section.tsx` (신규)
데이터: `fnrsCstList` 길이 4 (`planStpNo` 1~4).
검증(선택): 단계별 `gvexp+lcexp+orgpr+etc === sumAmt` (JSP `calculateTotalFunding` 참고).
---
### 4-H. 처리시설별 하수도정비기본계획 (`slide_14`) — 핵심 복잡도
**JSP 동작**
1. `BscFctList` 체크박스로 처리시설 다중 선택 (`arrBscFctCd`)
2. 선택 시 `createFormSection(code, name)`로 **당초 4단계 | 현재 | 변경 4단계** 매트릭스 동적 삽입
→ **React**: 세로 삽입 대신 `@common/components` `Tabs`로 시설별 패널 관리 (§아래 Tabs 규칙)
3. 활성 `formFields`(축소판): 처리구역면적, 하수처리인구, 계획하수량, 시설용량, 분류식 오수/우수, 합류식, 차집, 배수설비, 오수중계/일반/우수 펌프
4. 시설별 필수첨부 `createFileAttachmentSection`
5. `fetchBasicPlanData`로 당초(이전) 데이터 바인딩
**API 매핑**
| UI | DTO |
| -------------------- | ------------------------------------------------------------- |
| 선택 시설 목록 | `sewerBscPlanTmplntList[].swtrmtFcltCd` (+ `atchFileGroupId`) |
| 단계별 수치 (변경측) | `sewerBscPlanTmplntDtlList[]` → `SewerBscPlanTmplntDtlItem` |
| 임시저장 | `tmplntTmprStrgList` (편집 중 임시, 필요 시) |
필드 대응 (변경 단계 기준):
| UI 라벨 | `SewerBscPlanTmplntDtlItem` |
| -------------- | --------------------------- |
| 처리구역면적 | `prcsZoneArea` |
| 하수처리인구 | `swtrmtPpltnCnt` |
| 계획하수량 | `planSwqty` |
| 시설용량 | `fcltCpct` |
| 분류식 오수 | `sprsysDrtwPplLen` |
| 분류식 우수 | `sprsysExlnPplLen` |
| 합류식 | `cbsysPplLen` |
| 차집관로 | `icptgPplLen` |
| 배수설비 | `drgCnt` |
| 오수 중계 펌프 | `drtwRlyPumpCnt` |
| 오수 일반 펌프 | `drtwPumpCnt` |
| 우수 펌프 | `exlnPumpCnt` |
**구현 구성**
```
ui/sections/tmplnt-form-section.tsx
├─ TmplntFacilityPicker # BscFctList Checkbox 목록 (시설 마스터 API TBD)
└─ Tabs (@common/components Tabs) # 선택 시설별 동적 패널
├─ TabsList / TabsTrigger # value=swtrmtFcltCd, 라벨=시설명
└─ TabsContent (시설 1개당)
├─ TmplntMatrixTable # 당초|현재|변경 매트릭스 (useFieldArray)
└─ TmplntFacilityAttachments # 시설별 필수첨부 (선택)
```
**Tabs 관리 규칙** (`packages/common/components/src/ui/tabs`)
- `BscFctList` 체크 **선택** → `useFieldArray` append + 해당 시설 `TabsTrigger`/`TabsContent` 추가 → `Tabs` `value`를 신규 시설로 전환
- 체크 **해제** → confirm 후 remove + 해당 탭 제거 → 남은 탭 중 하나로 `value` 이동 (없으면 Tabs 영역 비표시)
- 선택 시설이 0개면 Tabs UI 숨김; 1개 이상이면 Tabs로 시설별 패널 전환
- 매트릭스·첨부는 각 `TabsContent` 안에만 렌더 (JSP의 세로 동적 삽입을 탭 패널로 대체)
**매트릭스 열**
- **당초 열**: 이전계획 API 조회 결과 읽기 전용 (없으면 빈 칸)
- **현재 열**: 기준년도 시점 값 (API 제공 시)
- **변경 열**: 편집 가능 → 저장 payload의 dtl만 전송
시설 마스터 목록 API: 레거시 `BscFctList` 대체 — **TBD** (`swtrmtFclt` 계열).
`bundle-dynamic-imports`: `TmplntFormSection`(또는 TabsContent 내부 매트릭스)을 `next/dynamic` 후보.
---
### 4-I. 체크리스트 + 첨부 (`slide_16`)
#### I-1. 체크리스트
**재사용**: `ChecklistFormSection`
`docSeComCd="<SEWER|DMBP TBD>"`
`fieldPrefix`: 마스터에서 `taskSeComCd`를 읽는 경로 (예: `sewerBscPlan`)
| JSP | React |
| ----------------------- | ------------------------------------------- |
| 업무탭 `ceckEssntlArr` | 코드 `taskSeComCd` 다중 → Tabs |
| 진행률 / 전체 예·아니오 | 위젯 내장 로직 |
| `exmntResult` Y/N | `cklstResultList[].selected` (+ `picRvwCn`) |
저장: `cklstResultList: CklstResultItem[]`
#### I-2. 필수 첨부 (탭별)
**재사용**: `AttachmentsFormSection`
`docSeComCd` 동일, `esntlAtchFileList`
레거시: 체크리스트 탭마다 `essntlList` + `uploadFile` multipart.
신규: TUS/`attach-service` + `esntlAtchFileId` / `atchFileGroupId` (fnrs와 동일).
#### I-3. 기타 첨부
`AttachmentsFormSection`의 etc 영역 또는 `atchFileGroupId` on `sewerBscPlanDtl`.
JSP `uploadFileETC` multiple → Zip 안내 문구 유지.
---
### 4-J. 저장 / 제출
**UI 구성**: [`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx) 과 **동일**.
마크업·분기(`isSplmntPrgrsStts` / `isEditMode`)·버튼 색상·`disabled={isSubmitting}` 패턴을 그대로 두고, **호출 API·목록 path만 sewer** 로 교체한다.
```tsx
<div className="mx-4 flex flex-wrap justify-center gap-3 sm:gap-4">
<Button color="white" type="button" onClick={() => router.push("/sewer-bsc-plan")} disabled={isSubmitting}>
목록
</Button>
{!isSplmntPrgrsStts ? (
<>
<Button type="submit" disabled={isSubmitting}>저장</Button>
{isEditMode && (
<>
<Button color="green" type="button" onClick={handleApplyClick} disabled={isSubmitting}>
신청완료
</Button>
<Button color="red" type="button" onClick={handleDelClick} disabled={isSubmitting}>
삭제
</Button>
</>
)}
</>
) : (
<Button type="button" onClick={form.handleSubmit(onSplmntCmptn)} disabled={isSubmitting}>
보완완료
</Button>
)}
</div>
```
| 버튼 | fnrs 대응 | sewer API / 동작 |
| -------- | ----------------------------- | ------------------------------------------------------------ |
| 목록 | `router.push("/fnrs-cnsltn")` | `router.push("/sewer-bsc-plan")` |
| 저장 | `saveApply` + 임시저장 | `useSaveApplySewerBscPlan` + `prgrsSttsComCd`=임시저장(`SV`) |
| 신청완료 | `handleApplyClick` → 제출 | 체크리스트+필수첨부 검증 → `saveApply` + `LE` (상태코드 TBD) |
| 삭제 | `handleDelClick` | `useDeleteSewerBscPlan`(또는 동등) → 목록 |
| 보완완료 | `onSplmntCmptn` | `useSubmitSupplementSewerBscPlan` |
| JSP | 진행값 | URL | React |
| ---- | ---------- | ------------------------- | ---------------------------------------------------------- |
| 저장 | `SAVE` | `insertFnrscDmbp.do` | `saveApply` + 임시저장 코드 |
| 제출 | `PRESENTN` | `insertFnrscDscss.do` (!) | `saveApply` + 제출 상태코드 — **백엔드 상태코드 확인 TBD** |
페이로드 조립 (`buildApplyPayload`):
```ts
SewerBscPlanApplySaveRequestDto {
sewerBscPlan, sewerBscPlanDtl,
sewerBscPlanOtlnList, bizCstList, fnrsCstList,
sewerBscPlanTmplntList, tmplntTmprStrgList?,
cklstResultList, esntlAtchFileList,
splmntDmndList?, rvwOpnnList? // 등록 화면에서는 보통 빈 배열
}
```
성공 후: `invalidateQueries(sewerBscPlanBaseKey)` → `router.push('/sewer-bsc-plan')` (신청완료는 상세로 보낼 수 있음 — fnrs와 동일 정책).
권한: JSP `AUTH3` → 기존 AS 권한 훅/역할로 버튼 `disabled`/`hidden`.
---
## 5. API / DTO 연결
Base: `/as-service/api/v1/sewer-bsc-plans`
엔티티: `@/entities/sewer-bsc-plan`
| 화면 동작 | Hook / Service | 비고 |
| -------------- | ---------------------------------------- | ---------------- |
| 등록 저장·제출 | `useSaveApplySewerBscPlan` → `saveApply` | 본 문서 핵심 |
| 수정 로드 | `useSewerBscPlanDetails(docNo, chgCycl)` | 편집 시 |
| 마스터만 | `useSewerBscPlan` | 목록 행 클릭 등 |
| 보완 제출 | `useSubmitSupplementSewerBscPlan` | edit + `prgrsSttsComCd=SL` 시 폼 하단 [보완완료] |
| 기술검토 | `useTechReviewSewerBscPlan` | 등록 JSP 범위 밖 |
등록 JSP에 없는 섹션(보완요구·검토의견)은 **상세/검토 화면**에서 `SplmntDmndFormSection` / 검토의견 섹션으로 후속 구현.
---
## 6. 권장 파일 배치
```
apps/as/
├── app/(with-base-layout)/sewer-bsc-plan/
│ └── regist/page.tsx
├── src/pages/sewer-bsc-plan/
│ ├── lib/
│ │ ├── sewer-bsc-plan-regist-schema.ts # zod
│ │ └── form-data-mapper.ts # Det ↔ Form, Apply payload
│ ├── model/
│ │ └── (필요 시 table columns)
│ └── ui/
│ ├── sewer-bsc-plan-regist-page.tsx
│ ├── sewer-bsc-plan-form-page.tsx
│ ├── components/
│ │ └── previous-plan-lookup-dialog.tsx
│ └── sections/
│ ├── basic-info-form-section.tsx
│ ├── outline-form-section.tsx # 사업개요
│ ├── biz-cst-matrix-form-section.tsx # 총사업비 (전용)
│ ├── fnrs-cst-form-section.tsx # 재원분담
│ └── tmplnt-form-section.tsx # 처리시설 (Checkbox + Tabs)
└── docs/sewer-bsc-plan/
└── dmbp-regist-migration.md # 본 문서
```
공통 재사용 (신규 작성 최소화):
- `ContactInfoFormSection`, `PrivacyConsentSection`
- `ChecklistFormSection`, `AttachmentsFormSection`
- `SectionCollapsible`
- `Tabs` / `TabsList` / `TabsTrigger` / `TabsContent` (`@common/components` — 처리시설 패널)
도메인 전용 (재사용 금지):
- `BizCstMatrixFormSection` — 총사업비 (fnrs `BusinessCostFormSection` 미사용)
- 폼 하단 액션바 — `fnrs-form-page` 279~323 복제, API만 sewer
---
## 7. 화면 데이터 흐름
```
BEGIN
// 최초 진입 (등록)
CALL 로그인사용자조회
CALL 코드조회(SEWERBSCPLANSE, SEWER, …) // 병렬
CALL 체크리스트트리(docSeComCd) // 병렬
CALL 필수첨부트리(docSeComCd) // 병렬
CALL 처리시설마스터목록 // TBD (BscFctList)
DISPLAY 폼 defaultValues
ON 1단계목표연도 CHANGE DO
SET 2·3·4단계 연도 (+5/+10/+15)
DISPLAY E/F/G/H 헤더 연도
END ON
ON 처리시설 체크 DO
ADD tmplntList 항목
ADD TabsTrigger / TabsContent (시설 패널)
SET Tabs value = 신규 시설
CALL 이전기본계획수치(시설코드) // 당초열, TBD
END ON
ON 처리시설 체크해제 DO
CONFIRM 제거
REMOVE tmplntList + 해당 Tabs 패널
SET Tabs value = 남은 시설 (없으면 Tabs 숨김)
END ON
ON [이전기본계획조회] CLICK DO
OPEN PreviousPlanLookupDialog
END ON
ON [저장] CLICK DO
VALIDATE zod (동의 제외 가능)
CALL saveApply(payload, 임시저장 상태)
NAVIGATE 목록
END ON
ON [신청완료] CLICK DO
VALIDATE zod + privacyAgree=Y + 체크리스트 완료 + 필수첨부(esntlAtchYn=Y) 완료
CONFIRM 신청 확인
CALL saveApply(payload, 제출 상태 LE)
NAVIGATE 상세
END ON
END
```
---
## 8. JSP ↔ 신규 갭·리스크
| # | 이슈 | 대응 |
| --- | ---------------------------------------------------- | ------------------------------------------------- |
| 1 | JSP 제출 URL이 `dscss`로 분기 (오타/구프로세스 가능) | 신규는 `saveApply` 단일 진입으로 통일 |
| 2 | 총사업비 UI 계층 ≠ `BizCstItem` | `BizCstMatrixFormSection` 전용 + `acntgSeCd` 매핑 테이블 |
| 3 | 재원분담 ≠ fnrs shar | 전용 `fnrs-cst-form-section` |
| 4 | 처리시설 매트릭스(당초/현재/변경) | Checkbox 선택 → Tabs 패널; 저장은 변경 dtl 중심; 당초는 조회 전용 |
| 5 | 시설·이전계획 API | TBD — 미정 시 체크박스 목록 mock(`faker`)만 |
| 6 | `docSeComCd` 값 | CMS/체크리스트 마스터와 합의 |
| 7 | 레거시 multipart vs TUS | fnrs 첨부 패턴 준수 |
| 8 | 주석 처리된 상세 `formFields` (자연발생인구 등) | **축소된 formFields만** 이관; 주석 블록 복원 금지 |
---
## 9. 구현 순서 (권장)
1. 라우트 + `FormPage` 골격 + zod 스키마(마스터·dtl·동의) + 폼 하단 액션바(§4-J)
2. B·C·D 섹션 (공통 위젯 최대 활용)
3. E 사업개요 + G 재원분담 (4열 매트릭스 패턴 공유)
4. F 총사업비 (`BizCstMatrixFormSection` 전용)
5. I 체크리스트·첨부
6. H 처리시설 (`BscFctList` Checkbox + `@common/components` Tabs — 가장 복잡)
7. 저장/신청완료/삭제/보완완료 API 연동 (§4-J)
8. 이전계획 Dialog (API 확정 후)
---
## 10. 체크리스트 (jsp-migration-as)
- [ ] contents만 이관 (include 제외)
- [ ] vercel-react-best-practices 적용 (Query 병렬·파생상태·dynamic)
- [ ] `@common/components` + 필요 시 DataTable
- [ ] API·모델은 `@/entities/sewer-bsc-plan` 우선
- [ ] 목데이터는 `@faker-js/faker`만 (시설 마스터 등 TBD 구간)
- [ ] 로딩·빈·에러 상태
- [ ] `router.push`로 목록 복귀
---
## 11. 후속 문서
- 목록(`dmbpList.jsp`) / 상세(`dmbpView.jsp`) / 수정(`dmbpModify.jsp`)은 본 문서와 동일 섹션 체계로 별도 확장
- 화면설계서 SPEC 형식이 필요하면 `docs/sewer-bsc-plan/SPEC.md`로 분리 (영역 A~J 표 + API 표)
@@ -0,0 +1,407 @@
# 화면설계서: 결재선 관리
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 0.3 |
| 프로세스명 | UI설계 |
| 화면명 | 결재선 관리 |
| 화면 유형 | [v] 조회 [v] 입력 [v] 수정 [v] 삭제 [ ] 출력 |
| 메뉴 경로 | 디지털행정지원 > 시스템관리 > 결재선 관리 |
---
## 2. 화면 목적
전자결재 처리에 사용할 **결재서식**(템플릿)을 등록·관리하고, 각 서식별로 **결재선**(결재구분·결재자·순서)을 구성한다. 상단의 결재서식 목록에서 하나를 선택하면 하단 영역에 해당 서식의 결재선이 표시되며, 사용자는 **그리드 인라인 폼**으로 결재자를 등록·수정한다. **행 추가 또는 한 행 수정 중에는 다른 행을 편집할 수 없다**(단일 행 편집 잠금). 드래그로 순서를 변경한 뒤 **[결재선 저장]** 으로 서버에 반영한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ 페이지 타이틀: 결재선 관리 브레드크럼: 디지털행정지원 > … > │
├───────────────────────────────────────────────────────────────────────────────────────────────┤
│ [영역 A] 전자결재선 관리 목록 [서식 등록] │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ 전자결재서식아이디 | 결재서식명 | 문서구분 | 등록일시 | 관리 │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ TMPL-0001 (선택 행 강조) … [수정][삭제] │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
├───────────────────────────────────────────────────────────────────────────────────────────────┤
│ [영역 B] 결재선 관리 선택된 서식: TMPL-0001 [결재선 저장] │
│ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ [영역 B-2] 결재선 그리드 (인라인 편집 + 행 드래그 — @dnd-kit) │ │
│ │ ⋮⋮ | 결재선아이디 | 결재순서 | 결재구분 ▼ | 결재자 [선택] | 등록일시 | 관리 │ │
│ │ ⋮⋮ | APLN-0001 | [ 1 ] | [검토자 ▼] | [김민수 ][선택] | … | [저장][삭제] │ │
│ │ ⋮⋮ | (신규) | [ 2 ] | [승인자 ▼] | [ ][선택] | — | [저장][취소] │ │
│ └─────────────────────────────────────────────────────────────────────────────────────────┘ │
│ [행 추가] │
│ ※ 결재자 등록·수정은 그리드 셀에서 직접 입력. 검토인력만 [선택] 팝업/다이얼로그 │
│ ※ 행 추가·수정 중: 해당 행만 편집 가능, 나머지 행·[행 추가]·드래그 비활성 │
└───────────────────────────────────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
CALL GET /as-elapr-service/api/v1/elapr-tmplt
DISPLAY 결재서식 그리드 (영역 A)
DISPLAY 빈 상태 안내 (영역 B) // 서식 미선택 시 그리드·툴바 비활성
// 영역 A: 서식 행 선택
ON 영역 A 행 CLICK DO
SET 선택된 서식 = 행.elaprTmpltId
CALL GET /as-elapr-service/api/v1/elapr-apln?elaprTmpltId={id}
DISPLAY 결재선 그리드 (영역 B-2) — 조회 모드
END ON
// 영역 A: 신규 서식 등록
ON [서식 등록] 버튼 CLICK DO
OPEN 결재서식 등록 다이얼로그
ON 확인 DO
CALL POST /as-elapr-service/api/v1/elapr-tmplt
CALL GET /as-elapr-service/api/v1/elapr-tmplt
END ON
END ON
// 영역 A: 서식 수정·삭제 (다이얼로그 유지)
ON 영역 A [수정] / [삭제] DO
(기존과 동일 — PUT / DELETE elapr-tmplt)
END ON
// 영역 B: 행 추가 (인라인 신규 행)
ON [행 추가] 버튼 CLICK DO
VALIDATE 서식 선택됨
VALIDATE 편집 중인 행 없음 (editingRowKey == null)
ADD 영역 B-2 그리드 맨 아래 편집 가능 행
SET editingRowKey = rowKey (draft-{timestamp})
SET 편집상태 = 신규
SET aplnId = 미설정 (POST 저장 시 서버 fn_gen_apln_id 발급)
SET atrzSeqNo = 현재 행 수 + 1
SET atrzSeComCd, aprvrUuid = 빈 값
DISABLE 나머지 행 편집 · [행 추가] · 드래그 · [결재선 저장](정책에 따라)
END ON
// 영역 B-2: 기존 행 수정 진입
ON 행별 [수정] CLICK DO
VALIDATE 편집 중인 행 없음
SET editingRowId = 행.aplnId
SET 해당 행만 편집 모드
DISABLE 나머지 행 편집 · [행 추가] · 드래그
END ON
// 영역 B-2: 인라인 편집
ON 그리드 셀 변경 DO
VALIDATE 변경 대상 행 == editingRowId
SET 해당 행 dirty = true
END ON
ON 셀 [선택] (검토인력) CLICK DO
OPEN 검토인력 선택 다이얼로그 (단일 선택)
ON 선택 DO
SET 행.aprvrUuid = 선택인력.uuid
SET 행.aprvrNm = 선택인력.성명 // UI 표시용 (응답 DTO에 없음 — 로컬 보조 필드)
END ON
END ON
// 영역 B-2: 행별 저장
ON 행별 [저장] CLICK DO
VALIDATE atrzSeComCd, aprvrUuid, atrzSeqNo
IF 편집상태 = 신규 THEN
CALL POST /as-elapr-service/api/v1/elapr-apln
ELSE
CALL PUT /as-elapr-service/api/v1/elapr-apln/{aplnId}
END IF
CALL GET /as-elapr-service/api/v1/elapr-apln?elaprTmpltId={id}
CLEAR editingRowId
ENABLE 전체 행 조회 모드 · [행 추가] · 드래그
END ON
// 영역 B: 결재선 저장 (드래그 순서 일괄 반영)
ON [결재선 저장] 버튼 CLICK DO
VALIDATE 서식 선택됨
VALIDATE 편집 중인 행 없음
REORDER atrzSeqNo = 그리드 행 순서 (1..n)
FOR EACH dirty 또는 순서 변경된 행 DO
CALL PUT /as-elapr-service/api/v1/elapr-apln/{aplnId} // atrzSeqNo 등 반영
END FOR
FOR EACH 신규 미저장 행 DO
CALL POST /as-elapr-service/api/v1/elapr-apln
END FOR
CALL GET /as-elapr-service/api/v1/elapr-apln?elaprTmpltId={id}
TOAST "결재선이 저장되었습니다."
END ON
// 영역 B-2: 행 드래그
ON 영역 B-2 행 DRAG END DO
VALIDATE 편집 중인 행 없음
REORDER 그리드 행
SET 각 행 atrzSeqNo = 행 인덱스 + 1 (UI 즉시 반영, 서버는 [결재선 저장] 또는 행별 [저장])
END ON
// 영역 B-2: 행 삭제
ON 행별 [삭제] CLICK DO
CONFIRM "선택한 결재선을 삭제하시겠습니까?"
IF 서버 저장된 행 THEN
CALL DELETE /as-elapr-service/api/v1/elapr-apln/{aplnId}
END IF
REMOVE 그리드 행 (신규 미저장 행은 API 호출 없이 제거)
CALL GET /as-elapr-service/api/v1/elapr-apln?elaprTmpltId={id}
END ON
ON 행별 [취소] CLICK DO // 신규·수정 공통
IF 편집상태 = 신규 THEN REMOVE 임시 행
ELSE RESTORE 행 스냅샷 (수정 취소)
CLEAR editingRowId
ENABLE 전체 행 조회 모드 · [행 추가] · 드래그
END ON
END
```
---
## 4. 영역 A: 전자결재선 관리 목록
선택된 결재서식이 영역 B의 작업 대상이 된다. 행 클릭으로 선택 상태를 표시(좌측 컬러 바·배경 강조)한다.
### 4-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | ----------------------------------------------- |
| 섹션 타이틀 | 전자결재선 관리 목록 |
| 우측 버튼 | **[서식 등록]** — 결재서식 등록 다이얼로그 오픈 |
### 4-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| ------------------ | -------------- | ------ | --------------------------------------- |
| 전자결재서식아이디 | `elaprTmpltId` | string | 등록 시 서버(DB 함수) 자동 발급 |
| 결재서식명 | `elaprTmpltNm` | string | |
| 문서구분 | `docSeComNm` | string | 코드 `docSeComCd`, 상위코드 `ASDOCSECD` |
| 등록일시 | `regDt` | string | `yyyy-MM-dd HH:mm:ss` |
| 관리 | — | — | 행별 **[수정] · [삭제]** 버튼 |
- 행 클릭 시: `elaprTmpltId` 선택 → 영역 B 라벨·B-2 그리드 갱신.
- 페이지네이션: Spring `Pageable` (`page`, `size`, `sort`).
### 4-3. 버튼
| 버튼명 | 위치 | 동작 |
| --------- | -------------- | ----------------------------------------------- |
| 서식 등록 | 섹션 우측 상단 | 결재서식 등록 다이얼로그 오픈 |
| 수정 | 행별 | 결재서식 수정 다이얼로그 (기존 값 초기화) |
| 삭제 | 행별 | 확인 후 삭제. 선택 중인 서식이면 영역 B 초기화. |
### 4-4. 다이얼로그(개요)
| 다이얼로그명 | 트리거 | 입력 항목 | 비고 |
| ------------------ | ----------- | ----------------------------------------------- | ----------------------------------------- |
| 결재서식 등록 | [서식 등록] | `elaprTmpltNm`, `docSeComCd` | `elaprTmpltExpln` 선택, ID는 저장 후 응답 |
| 결재서식 수정 | 행별 [수정] | `elaprTmpltNm`, `docSeComCd`, `elaprTmpltExpln` | ID는 변경 불가 |
| 결재서식 삭제 확인 | 행별 [삭제] | (확인/취소) | |
---
## 5. 영역 B: 결재선 관리
영역 A에서 선택된 `elaprTmpltId` 기준으로 결재선을 관리한다. **등록·수정은 B-2 그리드 인라인 폼**으로 수행하며, 검토인력 지정만 **[선택]** 보조 다이얼로그를 사용한다.
### 5-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| 섹션 타이틀 | 결재선 관리 |
| 우측 라벨 | `선택된 서식: {elaprTmpltId}` — 미선택 시 영역 B 비활성 |
| 우측 버튼 | **[행 추가]** — 편집 중인 행이 없을 때만 활성 · **[결재선 저장]** — 편집 중인 행이 없을 때만 활성(순서·미저장 행 일괄 반영) |
### 5-2. 영역 B-2: 결재선 그리드 (인라인 편집)
#### 그리드 컬럼
| 컬럼명 | Field | 입력 UI (편집 모드) | 비고 |
| ------------- | ------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------- |
| (드래그 핸들) | — | — | `useSortable` 연동 |
| 결재선아이디 | `aplnId` | 읽기 전용 | 신규 행: `—`, 저장 후 서버 `aplnId` 표시 |
| 결재순서번호 | `atrzSeqNo` | 읽기 전용 (그리드 행 순서 1부터 표시) | 저장 시 행 순서 기준 자동 채번 |
| 결재구분 | `atrzSeComCd` | Select (`ATRZSECD` 공통코드) | 표시명 `atrzSeComNm` (조회 행) |
| 결재자 | `aprvrUuid` | 읽기 전용 이름 + **[선택]** 버튼 | 응답에 성명 없음 — 선택 시 로컬 `aprvrNm` 유지 |
| 등록일시 | `regDt` | 읽기 전용 | 신규 행은 `—` |
| 관리 | — | 편집 중인 행: **[저장] [취소]** · 조회 행: **[수정] [삭제]** (다른 행 편집 중이면 비활성) |
#### 인라인 편집 정책
| 항목 | 내용 |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **단일 행 편집** | **행 추가 또는 한 행 수정 중에는 다른 행을 편집할 수 없다.** `editingRowId` 로 현재 편집 행 1건만 허용. |
| 편집 잠금 시 비활성 | 다른 행: 셀·[수정]·[삭제] 비활성 · `[행 추가]` 비활성 · 행 드래그 비활성 · (선택) `[결재선 저장]` 비활성 |
| 편집 해제 | 행별 **[저장]** 성공 또는 **[취소]** 시 `editingRowId` 초기화 후 전체 조회 모드 복귀 |
| 조회 행 | 편집 중이 아닐 때 읽기 전용. **[수정]** 클릭 시 해당 행만 편집 모드 진입 |
| 신규 행 | `[행 추가]` 시 맨 아래 편집 행 1건 생성 (`editingRowId` = 임시 키) |
| 필수값 | `atrzSeComCd`, `aprvrUuid` |
| 순서 중복 | 저장 시 행 순서로 자동 채번하므로 중복 없음 |
| 결재구분 코드 | `ATRZSECD` — 예: `ATRZ01` 검토자, `ATRZ02` 승인자, `ATRZ03` 최종승인자 |
| **순서 제약** | `ATRZ01`은 최상단 블록, `ATRZ03`은 최하단 블록. 드래그 종료·저장 시 `validateAtrzOrder`로 검증, 위반 시 적용/저장 불가 (결재구분 변경 시에는 미검증) |
| 저장 단위 | 행별 **[저장]** (단건 POST/PUT) 또는 툴바 **[결재선 저장]** (일괄) |
#### 행 드래그 정렬 (`@dnd-kit` + `useSortable`)
| 항목 | 내용 |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| 목적 | 결재 단계 순서를 그리드에서 직접 변경 |
| 라이브러리 | `@dnd-kit/core`, `@dnd-kit/sortable` — 테이블 행 POC 후 확정 |
| 저장 시점 | 드래그는 클라이언트 상태만 변경 → **[결재선 저장]** 또는 행별 **[저장]** 시 `atrzSeqNo` PUT |
| 편집 중 | `editingRowKey != null` 이면 드래그 **불가** |
| 순서 제약 | 드래그 종료 시 `validateAtrzOrder` — 실패하면 순서 변경 미적용 + 안내. 결재구분 미선택 행은 드래그 불가 |
### 5-3. 보조 다이얼로그 (검토인력만)
| 다이얼로그명 | 트리거 | 결과 |
| ------------- | ----------------------- | ---------------------------------- |
| 검토인력 선택 | 그리드 결재자 셀 [선택] | `aprvrUuid` 및 UI용 `aprvrNm` 반영 |
> 결재자 **등록·수정 다이얼로그는 사용하지 않음** (v0.2 변경).
---
## 6. API/DTO 명세
### 6-1. 공통
| 항목 | 내용 |
| ----------- | ------------------------------------------------------------- |
| 서비스 | `as-elapr-service` |
| Base prefix | `/as-elapr-service/api/v1` (프록시 경유) |
| 인증 | JWT (`Authorization` 헤더) |
| 페이징 | Spring `Pageable`: `page`, `size`, `sort` → `PageResponse<T>` |
| 엔티티 | `@/entities/elapr` (`elaprTmpltService`, `elaprAplnService`) |
### 6-2. 전자결재서식 (영역 A) — `elapr-tmplt`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| --------- | ------ | ----------------------------- | ------------------------------------------ | ------------------ | ---------------------------- |
| 목록 조회 | GET | `/elapr-tmplt` | Query: `ElaprTmpltSearchParams` + Pageable | `Page<ElaprTmplt>` | `elaprTmpltService.findPage` |
| 단건 조회 | GET | `/elapr-tmplt/{elaprTmpltId}` | — | `ElaprTmplt` | `elaprTmpltService.findById` |
| 등록 | POST | `/elapr-tmplt` | `ElaprTmpltSaveRequest` | `ElaprTmplt` (201) | `elaprTmpltService.create` |
| 수정 | PUT | `/elapr-tmplt/{elaprTmpltId}` | `ElaprTmpltUpdateRequest` | `ElaprTmplt` | `elaprTmpltService.update` |
| 삭제 | DELETE | `/elapr-tmplt/{elaprTmpltId}` | — | 204 | `elaprTmpltService.remove` |
**ElaprTmplt (응답)**
| Field | 타입 | 설명 |
| ----------------- | ------- | ------------------ |
| `elaprTmpltId` | string | 전자결재서식아이디 |
| `elaprTmpltNm` | string | 결재서식명 |
| `elaprTmpltExpln` | string? | 서식 설명 |
| `docSeComCd` | string? | 문서구분 코드 |
| `docSeComNm` | string? | 문서구분명 |
| `regDt` | string? | 등록일시 |
**ElaprTmpltSaveRequest**: `elaprTmpltNm`\_, `elaprTmpltExpln?`, `docSeComCd?`
**ElaprTmpltUpdateRequest**: `elaprTmpltNm`\*, `elaprTmpltExpln?`, `docSeComCd?`
**ElaprTmpltSearchParams**: `elaprTmpltNm?`, `docSeComCd?`, `regDtStart?`, `regDtEnd?` + `page`, `size`, `sort`
### 6-3. 결재선 (영역 B) — `elapr-apln`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| --------- | ------ | ---------------------- | ------------------------------- | ----------------- | --------------------------- |
| 목록 조회 | GET | `/elapr-apln` | Query: `elaprTmpltId`, Pageable | `Page<ElaprApln>` | `elaprAplnService.findPage` |
| 단건 조회 | GET | `/elapr-apln/{aplnId}` | — | `ElaprApln` | `elaprAplnService.findById` |
| 등록 | POST | `/elapr-apln` | `ElaprAplnSaveRequest` | `ElaprApln` (201) | `elaprAplnService.create` |
| 수정 | PUT | `/elapr-apln/{aplnId}` | `ElaprAplnUpdateRequest` | `ElaprApln` | `elaprAplnService.update` |
| 삭제 | DELETE | `/elapr-apln/{aplnId}` | — | 204 | `elaprAplnService.remove` |
> **일괄 저장 API 없음** — UI의 [결재선 저장]은 변경된 행에 대해 POST/PUT을 순차(또는 병렬) 호출하는 클라이언트 오케스트레이션으로 구현.
**ElaprApln (응답)**
| Field | 타입 | 설명 |
| -------------- | ------- | ------------------ |
| `aplnId` | string | 결재선아이디 |
| `elaprTmpltId` | string | 전자결재서식아이디 |
| `aprvrUuid` | string? | 결재자 UUID |
| `atrzSeComCd` | string? | 결재구분 코드 |
| `atrzSeComNm` | string? | 결재구분명 |
| `atrzSeqNo` | number? | 결재순서번호 |
| `regDt` | string? | 등록일시 |
**ElaprAplnSaveRequest**: `elaprTmpltId`\_, `aprvrUuid?`, `atrzSeComCd?`, `atrzSeqNo?`
**ElaprAplnUpdateRequest**: `elaprTmpltId`\*, `aprvrUuid?`, `atrzSeComCd?`, `atrzSeqNo?`
### 6-4. 검토인력 (보조)
| 기능 | Method | URL | 비고 |
| ------------------ | ------ | --- | --------------------------- |
| 검토인력 목록 조회 | TBD | TBD | 사용자 서비스 또는 별도 API |
### 6-5. Backend ↔ 화면 필드 매핑 (구 SPEC 대비)
| 구 SPEC 필드 | 백엔드 필드 |
| ----------------- | ---------------------------------------------------- |
| `elctAtrzTmpltId` | `elaprTmpltId` |
| `atrzTmpltNm` | `elaprTmpltNm` |
| `docSeNm` | `docSeComNm` |
| `atrzLineId` | `aplnId` |
| `atrzSeNm` | `atrzSeComNm` |
| `atrzUserNm` | (없음) `aprvrUuid` + 사용자 조회/선택 시 로컬 표시명 |
---
## 7. UI 구현 가이드
### 7-1. 기술 스택·원칙
- **공통 컴포넌트**: `@common/components`
- **테이블**: `@tanstack/react-table` + 인라인 편집 셀 (`Select`, `Input`, `Button`)
- **드래그**: `@dnd-kit` — B-2 행 정렬 (POC 필수)
- **데이터**: React Query — `@/entities/elapr` hooks
- **상태**: 선택된 `elaprTmpltId`, `editingRowId` (단일 행 편집 잠금), B-2 행 배열(dirty·신규·`aprvrNm`·수정 전 스냅샷)
- **모의 데이터**: API 연동 전 `@faker-js/faker`
### 7-2. 모듈·파일 배치(제안)
| 구분 | 경로 |
| ------ | -------------------------------------------------------------------------------------- |
| 라우트 | `apps/as/app/(with-base-layout)/approval-line-management/page.tsx` |
| 페이지 | `apps/as/src/pages/approval-line-management/` (Public API: page만 export) |
| UI | `.../ui/elapr-tmplt-list.tsx`, `elapr-tmplt-form-dialog.tsx`, `elapr-apln-grid.tsx` 등 |
| model | `.../model/columns.tsx`, `apln-row-types.ts`, `mock-reviewers.ts` |
| lib | `.../lib/use-apln-rows.ts`, `validators.ts`, `use-dnd-sensors.ts` |
| 엔티티 | `apps/as/src/entities/elapr/` |
### 7-3. 영역별 UI 매핑
| 영역 | 주요 UI |
| ------ | -------------------------------------------------------------------------------------------- |
| A | DataTable + 행 선택 + 서식 등록/수정 다이얼로그 |
| B 헤더 | 상태 라벨 + `행 추가`, `결재선 저장` |
| B-2 | 인라인 편집 DataTable + `editingRowId` 단일 행 잠금 + 드래그 핸들 + 행별 수정/저장/삭제/취소 |
| 공통 | 서식 Dialog, 검토인력 선택 Dialog만 |
### 7-4. 데이터 흐름(요약)
- **진입**: `useElaprTmpltPage` → 영역 A. B 비활성.
- **서식 선택**: `useElaprAplnPage({ elaprTmpltId })` → B-2.
- **행 추가**: `editingRowId` 없을 때만 가능 → 신규 행 1건 편집 잠금 → 저장/취소 후 해제.
- **인라인 수정**: [수정]으로 `editingRowId` 설정 → 해당 행만 편집 → `useUpdateElaprApln` 후 해제.
- **드래그**: `editingRowId == null` 일 때만 가능 → 로컬 `atrzSeqNo` 재부여 → 일괄 PUT.
- **삭제**: `useDeleteElaprApln` 또는 신규 행 로컬 제거.
### 7-5. 미확정 / 검증 필요
- 검토인력 API 경로·DTO.
- `@dnd-kit` + react-table 행 인라인 Input 동시 적용 POC.
- 결재자 성명 표시: `aprvrUuid` → 사용자명 조회 API.
- 영역 A·B 페이지네이션·검색 UI 노출 여부.
- 권한(역할별 CRUD).
@@ -0,0 +1,487 @@
# 화면설계서: 체크리스트관리
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 0.1 |
| 프로세스명 | UI설계 |
| 화면명 | 체크리스트관리 |
| 화면 유형 | [v] 조회 [v] 입력 [v] 수정 [v] 삭제 [ ] 출력 |
| 메뉴 경로 | 디지털행정지원 > 시스템관리 > 체크리스트관리 |
---
## 2. 화면 목적
업무별·문서별 **점검표**(체크리스트)를 등록·관리하고, **점검항목** 마스터를 유지한다. 상단 좌측에서 점검표를 선택하면 하단 **체크리스트 항목** 영역에 해당 점검표의 검토 구성(계층형 검토내용)을 편집한다. 상단 우측 **점검항목 관리**는 점검표에 연결할 항목 풀을 등록·수정·삭제한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ 페이지 타이틀: 체크리스트관리 브레드크럼: 디지털행정지원 > … > │
├──────────────────────────────────────────────┬────────────────────────────────────────────────┤
│ [영역 A] 점검표 관리 │ [영역 B] 점검항목 관리 │
│ [점검표 추가] │ [점검항목 추가] │
│ ┌────────────────────────────────────────┐ │ ┌──────────────────────────────────────────┐ │
│ │ 점검표일련번호 | 점검표명 | 문서구분 │ │ │ 점검항목코드 | 점검항목명 | 사용여부 │ │
│ │ 업무구분 | 사용여부 | 등록일시 | 관리 │ │ │ 등록일시 | 관리 │ │
│ ├────────────────────────────────────────┤ │ ├──────────────────────────────────────────┤ │
│ │ 1 (선택 행 강조) [관리] [삭제] │ │ │ ITEM_001 요건검토 … [관리][삭제] │ │
│ │ │ │ │ ITEM_002 법령검토 … [관리][삭제] │ │
│ └────────────────────────────────────────┘ │ └──────────────────────────────────────────┘ │
├──────────────────────────────────────────────┴────────────────────────────────────────────────┤
│ [영역 C] 체크리스트 항목 [미리보기] [추가] [저장] │
│ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 점검항목 ▼ | 주요 검토내용 | 사용 여부 | 구분 | 관리 │ │
│ │ L1 [요건검토 ▼] | (입력) | ○예 ○아니오 | 1단계 | [하위추가]│ │
│ │ L2 [요건검토 ▼] | (입력) | ○예 ○아니오 | 2단계 | [삭제] │ │
│ └─────────────────────────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
CALL GET /as-checklist-service/api/v1/cklst
DISPLAY 점검표 그리드 (영역 A)
CALL GET /as-checklist-service/api/v1/cklst-artcl
DISPLAY 점검항목 그리드 (영역 B)
DISPLAY 빈 상태 안내 (영역 C) // 점검표 미선택 시 C 비활성
// 영역 A: 점검표 행 선택
ON 영역 A 행 CLICK DO
SET 선택된 점검표 = 행.cklstSn
CALL GET /as-checklist-service/api/v1/cklst-rvw?cklstSn={cklstSn}
// TBD: 점검표 단위 전체 검토 트리 조회 API — 현재는 항목별 by-artcl 조합 또는 클라이언트 병합
DISPLAY 체크리스트 항목 그리드 (영역 C)
END ON
// 영역 A: 점검표 추가
ON [점검표 추가] 버튼 CLICK DO
OPEN 점검표 추가 다이얼로그
ON [저장] DO
VALIDATE cklstNm, taskSeComCd
CALL POST /as-checklist-service/api/v1/cklst
CALL GET /as-checklist-service/api/v1/cklst
CLOSE 다이얼로그
END ON
ON [취소] / [닫기] DO
CLOSE 다이얼로그
END ON
END ON
// 영역 A: 점검표 수정·삭제
ON 영역 A 행 [관리] CLICK DO
OPEN 점검표 수정 다이얼로그 (기존 값 초기화)
ON [저장] DO
CALL PUT /as-checklist-service/api/v1/cklst/{cklstSn}
CALL GET /as-checklist-service/api/v1/cklst
END ON
END ON
ON 영역 A 행 [삭제] CLICK DO
CONFIRM "선택한 점검표를 삭제하시겠습니까?"
CALL DELETE /as-checklist-service/api/v1/cklst/{cklstSn}
IF 삭제 대상 == 선택된 점검표 THEN RESET 영역 C
CALL GET /as-checklist-service/api/v1/cklst
END ON
// 영역 B: 점검항목 추가
ON [점검항목 추가] 버튼 CLICK DO
OPEN 점검항목 추가 다이얼로그
ON [저장] DO
VALIDATE cklstArtclNm
// TBD: cklstSn 필수 — UI에 소속 점검표 선택 없음. 백엔드 정책 확인
CALL POST /as-checklist-service/api/v1/cklst-artcl
CALL GET /as-checklist-service/api/v1/cklst-artcl
CLOSE 다이얼로그
END ON
END ON
// 영역 B: 점검항목 수정·삭제
ON 영역 B 행 [관리] CLICK DO
OPEN 점검항목 수정 다이얼로그
ON [저장] DO
CALL PUT /as-checklist-service/api/v1/cklst-artcl/{cklstArtclSn}/{cklstSn}
END ON
END ON
ON 영역 B 행 [삭제] CLICK DO
CONFIRM "선택한 점검항목을 삭제하시겠습니까?"
CALL DELETE /as-checklist-service/api/v1/cklst-artcl/{cklstArtclSn}/{cklstSn}
CALL GET /as-checklist-service/api/v1/cklst-artcl
END ON
// 영역 C: 검토 행 추가
ON [추가] 버튼 CLICK DO
VALIDATE 선택된 점검표 존재
ADD 영역 C 그리드 맨 아래 편집 가능 행 (1단계, rvwCnLvNo=1)
END ON
// 영역 C: 하위 검토 추가
ON 행 [하위추가] CLICK DO
ADD 부모행 하위 편집 행 (2단계, upCklstRvwSn=부모.cklstRvwSn, rvwCnLvNo=2)
END ON
// 영역 C: 일괄 저장
ON [저장] 버튼 CLICK DO
VALIDATE 선택된 점검표 존재
FOR EACH 신규·변경된 검토 행 DO
IF 신규 THEN
CALL POST /as-checklist-service/api/v1/cklst-rvw
ELSE
CALL PUT /as-checklist-service/api/v1/cklst-rvw/{cklstRvwSn}/{cklstArtclSn}/{cklstSn}
END IF
END FOR
CALL GET /as-checklist-service/api/v1/cklst-rvw?cklstSn={cklstSn}
TOAST "체크리스트 항목이 저장되었습니다."
END ON
// 영역 C: 행 삭제
ON 행 [삭제] CLICK DO
CONFIRM "선택한 항목을 삭제하시겠습니까?"
IF 서버 저장된 행 THEN
CALL DELETE /as-checklist-service/api/v1/cklst-rvw/{cklstRvwSn}/{cklstArtclSn}/{cklstSn}
END IF
REMOVE 그리드 행 (미저장 행은 API 호출 없이 제거)
END ON
// 영역 C: 미리보기
ON [미리보기] 버튼 CLICK DO
OPEN 체크리스트 미리보기 다이얼로그 (읽기 전용 트리)
// TBD: 미리보기 UI·API
END ON
END
```
---
## 4. 영역 A: 점검표 관리
선택된 점검표가 영역 C의 편집 대상이 된다. 행 클릭으로 선택 상태를 표시한다.
### 4-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | ------------------------------------------ |
| 섹션 타이틀 | 점검표 관리 |
| 우측 버튼 | **[점검표 추가]** — 점검표 등록 다이얼로그 |
### 4-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| ---------------- | ------------- | ------ | ---------------------------------------------------- |
| 점검표일련번호 | `cklstSn` | number | PK |
| 점검표명 | `cklstNm` | string | |
| 문서구분공통코드 | `docSeComCd` | string | 코드명 표시 — `ASDOCSECD` 등 공통코드 (TBD 상위코드) |
| 업무구분공통코드 | `taskSeComCd` | string | RE/SF/SP 등 — 공통코드 (TBD 상위코드) |
| 사용여부 | `useYn` | string | `Y`→예, `N`→아니오 |
| 등록일시 | `regDt` | string | `yyyy-MM-dd HH:mm:ss` |
| 관리 | — | — | 행별 **[관리] · [삭제]** |
- 행 클릭 시: `cklstSn` 선택 → 영역 C 헤더·그리드 갱신.
- 페이지네이션: Spring `Pageable` (`page`, `size`, `sort`).
### 4-3. 버튼
| 버튼명 | 위치 | 동작 |
| ----------- | -------------- | ------------------------------------------------- |
| 점검표 추가 | 섹션 우측 상단 | 점검표 추가 다이얼로그 오픈 |
| 관리 | 행별 | 점검표 수정 다이얼로그 (기존 값 초기화) |
| 삭제 | 행별 | 확인 후 삭제. 선택 중인 점검표이면 영역 C 초기화. |
### 4-4. 다이얼로그: 점검표 추가 / 수정
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| -------- | ---------------- | ---- | ------ | ----------------- | ------------------------------------------------ |
| 점검표명 | Text | Y | — | `cklstNm` | placeholder: "예: 설치인가 체크리스트" |
| 문서구분 | Select(공통코드) | N | — | `docSeComCd` | 예: FNRS, INSTL, PWRK, DESIGN |
| 업무구분 | Select(공통코드) | Y | — | `taskSeComCd` | 예: RE(재이용수), SF(하수처리시설), SP(하수관로) |
| 사용여부 | Select | N | Y | `useYn` | Y/N, UI 표시: 예/아니오 |
| 버튼명 | 동작 |
| ------ | -------------------------------------- |
| 저장 | POST(등록) 또는 PUT(수정) 후 목록 갱신 |
| 취소 | 다이얼로그 닫기, 입력값 폐기 |
| 닫기 | 다이얼로그 닫기 |
---
## 5. 영역 B: 점검항목 관리
점검표 하단(영역 C)에서 선택할 **점검항목** 마스터를 관리한다.
### 5-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | ---------------------------------------------- |
| 섹션 타이틀 | 점검항목 관리 |
| 우측 버튼 | **[점검항목 추가]** — 점검항목 등록 다이얼로그 |
### 5-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| ------------ | -------------- | ------ | ------------------------------------- |
| 점검항목코드 | `cklstArtclSn` | number | UI mock `ITEM_001` 형식 표시 여부 TBD |
| 점검항목명 | `cklstArtclNm` | string | |
| 사용여부 | `useYn` | string | `Y`→예, `N`→아니오 |
| 등록일시 | `regDt` | string | `yyyy-MM-dd HH:mm:ss` |
| 관리 | — | — | 행별 **[관리] · [삭제]** |
> **TBD**: 백엔드 `CklstArtcl`은 복합 PK(`cklstArtclSn`, `cklstSn`)이며 등록 시 `cklstSn` 필수. UI 다이얼로그에는 `cklstSn` 입력이 없음 — 소속 점검표 지정 방식·전역 마스터 여부 백엔드 정책 확인.
### 5-3. 다이얼로그: 점검항목 추가 / 수정
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| ---------- | --------- | ---- | ------ | ----------------- | --------------------------- |
| 점검항목명 | Text | Y | — | `cklstArtclNm` | placeholder: "예: 법령검토" |
| 사용여부 | Select | N | Y | `useYn` | Y/N |
| 버튼명 | 동작 |
| ------ | -------------------------------------- |
| 저장 | POST(등록) 또는 PUT(수정) 후 목록 갱신 |
| 취소 | 다이얼로그 닫기 |
| 닫기 | 다이얼로그 닫기 |
---
## 6. 영역 C: 체크리스트 항목 (선택된 점검표)
영역 A에서 선택된 `cklstSn` 기준으로 **점검표검토**(계층형 검토내용)를 구성한다.
### 6-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | -------------------------------------------------------------------- |
| 섹션 타이틀 | 체크리스트 항목 (선택된 점검표: {cklstNm}) |
| 우측 버튼 | **[미리보기]** · **[추가]** · **[저장]** — 점검표 미선택 시 C 비활성 |
### 6-2. 그리드 컬럼
| 컬럼명 | Field | 입력 UI | 비고 |
| ------------- | -------------- | ------------------------------------- | ------------------------------------ |
| 점검항목 | `cklstArtclSn` | Select (영역 B 항목 목록) | L1/L2 접두 표시, `cklstArtclNm` 라벨 |
| 주요 검토내용 | `rvwCn` | Text / Textarea | 최대 4000자 |
| 사용 여부 | `useYn` | Radio (예/아니오) | `Y` / `N` |
| 구분 | `rvwCnLvNo` | 읽기 전용 | 1→1단계, 2→2단계, 3→3단계 |
| 관리 | — | **[하위추가]** (1·2단계, 저장 후) · **[삭제]** | 3단계 행은 [하위추가] 미표시 |
#### 계층 정책
| 항목 | 내용 |
| -------------- | -------------------------------------------------------------------- |
| 최대 깊이 | 3단계 (`rvwCnLvNo`: 1, 2, 3) |
| 상위-하위 연결 | `upCklstRvwSn` — 2단계 행은 부모 1단계 `cklstRvwSn` 참조 |
| 정렬 | `sortSeq` — 동일 레벨 내 순서 (TBD: UI 드래그 정렬 여부) |
| 체크 사용 | `bchckUseYn` — 미리보기·실사용 화면 체크박스 노출 여부 (TBD UI 반영) |
### 6-3. 버튼
| 버튼명 | 동작 |
| -------- | ---------------------------------------------------- |
| 추가 | 1단계 검토 행 1건 추가 (편집 가능) |
| 저장 | 변경·신규 검토 행 일괄 POST/PUT (일괄 저장 API 없음) |
| 미리보기 | 선택 점검표 체크리스트 읽기 전용 미리보기 (TBD) |
| 하위추가 | 선택 1·2단계 행 아래 하위 단계(2·3단계) 자식 행 추가 |
| 삭제 | 확인 후 DELETE 또는 미저장 행 로컬 제거 |
---
## 7. API/DTO 명세
### 7-1. 공통
| 항목 | 내용 |
| ------------- | ------------------------------------------------------------- |
| 서비스 | `as-checklist-service` |
| Base prefix | `/as-checklist-service/api/v1` (게이트웨이 프록시 경유) |
| 인증 | JWT (`Authorization` 헤더), `X-Site-Id`, `token-id` |
| 페이징 | Spring `Pageable`: `page`, `size`, `sort` → `PageResponse<T>` |
| 엔티티 | `@/entities/cklst` (`cklstMngService`, `cklstArtclService`, `cklstRvwService`) |
### 7-2. 점검표 (영역 A) — `cklst`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| --------- | ------ | ------------------ | --------------------------------- | ------------------ | -------------------------- |
| 목록 조회 | GET | `/cklst` | `CklstMngSearchParams` + Pageable | `Page<CklstMng>` | `cklstMngService.findPage` |
| 단건 조회 | GET | `/cklst/{cklstSn}` | — | `CklstMng` | `cklstMngService.findById` |
| 등록 | POST | `/cklst` | `CklstMngSaveRequest` | `number` (cklstSn) | `cklstMngService.create` |
| 수정 | PUT | `/cklst/{cklstSn}` | `CklstMngSaveRequest` | `number` (cklstSn) | `cklstMngService.update` |
| 삭제 | DELETE | `/cklst/{cklstSn}` | — | `number` (cklstSn) | `cklstMngService.remove` |
**CklstMng (응답)**
| Field | 타입 | 설명 |
| ------------- | ------- | ---------------- |
| `cklstSn` | number | 점검표일련번호 |
| `cklstNm` | string | 점검표명 |
| `taskSeComCd` | string | 업무구분공통코드 |
| `useYn` | string? | 사용여부 |
| `docSeComCd` | string? | 문서구분공통코드 |
| `rgtr` | string? | 등록자 UUID |
| `rgtrNm` | string? | 등록자명 |
| `mdfr` | string? | 수정자 UUID |
| `regDt` | string? | 등록일시 |
| `mdfcnDt` | string? | 수정일시 |
**CklstMngSaveRequest**: `cklstNm`\*, `taskSeComCd`\*, `useYn?`, `docSeComCd?`
**CklstMngSearchParams**: `cklstNm?`, `taskSeComCd?`, `useYn?`, `docSeComCd?` + `page`, `size`, `sort`
**React Query hooks**: `useCklstMngPage`, `useCklstMng`, `useCreateCklstMng`, `useUpdateCklstMng`, `useDeleteCklstMng`
### 7-3. 점검표항목 (영역 B) — `cklst-artcl`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| ------------------ | ------ | --------------------------------------- | ----------------------------------- | ------------------ | --------------------------------- |
| 목록 조회 (페이징) | GET | `/cklst-artcl` | `CklstArtclSearchParams` + Pageable | `Page<CklstArtcl>` | `cklstArtclService.findPage` |
| 점검표별 전체 목록 | GET | `/cklst-artcl/by-cklst/{cklstSn}` | — | `CklstArtcl[]` | `cklstArtclService.findByCklstSn` |
| 단건 조회 | GET | `/cklst-artcl/{cklstArtclSn}/{cklstSn}` | — | `CklstArtcl` | `cklstArtclService.findById` |
| 등록 | POST | `/cklst-artcl` | `CklstArtclSaveRequest` | `CklstArtclId` | `cklstArtclService.create` |
| 수정 | PUT | `/cklst-artcl/{cklstArtclSn}/{cklstSn}` | `CklstArtclUpdateRequest` | `CklstArtclId` | `cklstArtclService.update` |
| 삭제 | DELETE | `/cklst-artcl/{cklstArtclSn}/{cklstSn}` | — | `CklstArtclId` | `cklstArtclService.remove` |
**CklstArtcl (응답)**
| Field | 타입 | 설명 |
| ----------------- | ------- | ------------------ |
| `cklstArtclSn` | number | 점검표항목일련번호 |
| `cklstSn` | number | 점검표일련번호 |
| `cklstArtclNm` | string? | 점검표항목명 |
| `useYn` | string? | 사용여부 |
| `rgtr` / `rgtrNm` | string? | 등록자 |
| `regDt` | string? | 등록일시 |
**CklstArtclSaveRequest**: `cklstSn`\*, `cklstArtclNm?`, `useYn?`
**CklstArtclUpdateRequest**: `cklstArtclNm?`, `useYn?`
**CklstArtclId**: `{ cklstArtclSn: number; cklstSn: number }`
**React Query hooks**: `useCklstArtclPage`, `useCklstArtclList`, `useCklstArtcl`, `useCreateCklstArtcl`, `useUpdateCklstArtcl`, `useDeleteCklstArtcl`
### 7-4. 점검표검토 (영역 C) — `cklst-rvw`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| --------------------- | ------ | -------------------------------------------------- | ------------------------------ | ---------------- | ------------------------------- |
| 목록 조회 (페이징) | GET | `/cklst-rvw` | `CklstRvwSearchParams` + Pageable | `Page<CklstRvw>` | `cklstRvwService.findPage` |
| 항목별 검토 전체 목록 | GET | `/cklst-rvw/by-artcl/{cklstSn}/{cklstArtclSn}` | — | `CklstRvw[]` | `cklstRvwService.findByArtcl` |
| 단건 조회 | GET | `/cklst-rvw/{cklstRvwSn}/{cklstArtclSn}/{cklstSn}` | — | `CklstRvw` | `cklstRvwService.findById` |
| 등록 | POST | `/cklst-rvw` | `CklstRvwSaveRequest` | `CklstRvwId` | `cklstRvwService.create` |
| 수정 | PUT | `/cklst-rvw/{cklstRvwSn}/{cklstArtclSn}/{cklstSn}` | `CklstRvwUpdateRequest` | `CklstRvwId` | `cklstRvwService.update` |
| 삭제 | DELETE | `/cklst-rvw/{cklstRvwSn}/{cklstArtclSn}/{cklstSn}` | — | `CklstRvwId` | `cklstRvwService.remove` |
**CklstRvw (응답)**
| Field | 타입 | 설명 |
| -------------- | ------- | ---------------------- |
| `cklstRvwSn` | number | 점검표검토일련번호 |
| `cklstArtclSn` | number | 점검표항목일련번호 |
| `cklstSn` | number | 점검표일련번호 |
| `upCklstRvwSn` | number? | 상위점검표검토일련번호 |
| `rvwCnLvNo` | number? | 검토내용레벨번호 |
| `rvwCn` | string? | 검토내용 |
| `bchckUseYn` | string? | 체크사용여부 |
| `sortSeq` | number? | 정렬순서 |
| `useYn` | string? | 사용여부 |
| `regDt` | string? | 등록일시 |
**CklstRvwSaveRequest**: `cklstArtclSn`\*, `cklstSn`\*, `upCklstRvwSn?`, `rvwCnLvNo?`, `rvwCn?`, `bchckUseYn?`, `sortSeq?`, `useYn?`
**CklstRvwUpdateRequest**: `upCklstRvwSn?`, `rvwCnLvNo?`, `rvwCn?`, `bchckUseYn?`, `sortSeq?`, `useYn?`
**CklstRvwSearchParams**: `cklstSn?`, `cklstArtclSn?`, `useYn?` + `page`, `size`, `sort`
**CklstRvwId**: `{ cklstRvwSn: number; cklstArtclSn: number; cklstSn: number }`
**React Query hooks**: `useCklstRvwPage`, `useCklstRvwByArtcl`, `useCklstRvw`, `useCreateCklstRvw`, `useUpdateCklstRvw`, `useDeleteCklstRvw`
### 7-5. 공통코드 (보조)
| UI 필드 | 코드 그룹 (TBD) | 예시 값 |
| -------- | --------------- | -------------------------------------------- |
| 문서구분 | `ASDOCSECD` 등 | FNRS, INSTL, PWRK, DESIGN |
| 업무구분 | TBD | RE(재이용수), SF(하수처리시설), SP(하수관로) |
| 사용여부 | 고정 | Y, N |
공통코드 조회: `@/entities/code` — `codeService`, `CodeSelect`
---
## 8. UI 구현 가이드
### 8-1. 기술 스택·원칙
- **공통 컴포넌트**: `@common/components` (`Dialog`, `Button`, `Input`, `Select`, `RadioGroup` 등)
- **테이블**: `@tanstack/react-table` + `useDataTable`
- **데이터**: React Query — `@/entities/cklst` hooks (`useCklstRvwPage` 등 영역 C 포함)
- **공통코드**: `@/entities/code` — `CodeSelect`, `findCodeLabel`
- **상태**: 선택된 `cklstSn`, 영역 C 검토 행 배열(신규·dirty·계층 트리)
- **모의 데이터**: API 연동 전 `@faker-js/faker`
### 8-2. 모듈·파일 배치(제안)
| 구분 | 경로 |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 라우트 | `apps/as/app/(with-base-layout)/checklist-management/page.tsx` |
| 페이지 | `apps/as/src/pages/checklist-management/` (Public API: page만 export) |
| UI | `.../ui/cklst-mng-list.tsx`, `cklst-mng-form-dialog.tsx`, `cklst-artcl-list.tsx`, `cklst-artcl-form-dialog.tsx`, `cklst-rvw-grid.tsx`, `cklst-preview-dialog.tsx` |
| model | `.../model/cklst-mng-columns.tsx`, `cklst-artcl-columns.tsx`, `cklst-rvw-row-types.ts` |
| lib | `.../lib/use-cklst-rvw-rows.ts`, `validators.ts`, `build-rvw-tree.ts` |
| 엔티티 | `apps/as/src/entities/cklst/` (`cklstMng`, `cklstArtcl`, `cklstRvw` 서비스·hooks) |
| 페이지 | `apps/as/src/pages/checklist-management/` — `/checklist-management` |
| 문서 | `apps/as/docs/checklist-management/SPEC.md` |
### 8-3. 영역별 UI 매핑
| 영역 | 주요 UI |
| ---- | ---------------------------------------------------------------------------- |
| A | DataTable + 행 선택 + 점검표 추가/수정 Dialog |
| B | DataTable + 점검항목 추가/수정 Dialog |
| C | 계층형 편집 그리드(1·2단계) + [추가][저장][미리보기] + 행별 [하위추가][삭제] |
| 공통 | `CodeSelect` (문서구분·업무구분), 사용여부 Y/N Select/Radio |
### 8-4. 다이얼로그·라우팅
| 다이얼로그명 | 트리거 | API 연동 |
| ------------------- | --------------- | ------------------------- |
| 점검표 추가 | [점검표 추가] | `useCreateCklstMng` |
| 점검표 수정 | 행 [관리] | `useUpdateCklstMng` |
| 점검표 삭제 확인 | 행 [삭제] | `useDeleteCklstMng` |
| 점검항목 추가 | [점검항목 추가] | `useCreateCklstArtcl` |
| 점검항목 수정 | 행 [관리] | `useUpdateCklstArtcl` |
| 체크리스트 미리보기 | [미리보기] | TBD — 읽기 전용 트리 렌더 |
라우트: `/checklist-management` (메뉴 URL은 CMS 등록값과 일치시킴)
### 8-5. 데이터 흐름(요약)
- **진입**: `useCklstMngPage` → 영역 A, `useCklstArtclPage` → 영역 B. C 비활성.
- **점검표 선택**: `cklstSn` 설정 → 영역 C 활성 → `useCklstRvwPage({ cklstSn })` → 트리 그리드 표시.
- **점검표 CRUD**: Dialog → `useCreateCklstMng` / `useUpdateCklstMng` / `useDeleteCklstMng`.
- **점검항목 CRUD**: Dialog → `useCreateCklstArtcl` / `useUpdateCklstArtcl` / `useDeleteCklstArtcl`.
- **검토 저장**: [저장] → 신규 POST / 기존 PUT 순차 호출 (일괄 API 없음).
- **영역 C 점검항목 Select**: `useCklstArtclList(cklstSn)` 또는 영역 B 전체 목록과 교차 — **TBD**.
### 8-6. 미확정 / 검증 필요
- [ ] 영역 B 점검항목 등록 시 `cklstSn` 필수 vs UI 무소속 마스터 — 백엔드·기획 확인
- [ ] `점검항목코드`(ITEM_001) 표시 형식 vs `cklstArtclSn` 숫자 PK
- [ ] 영역 C 전체 검토 트리 조회: `cklstSn` 단위 일괄 API 부재 — `by-artcl` 다건 호출 또는 백엔드 확장
- [x] `@/entities/cklst` — `cklstRvwService`·hooks 구현
- [x] `pages/checklist-management` — 영역 A·B·C UI 구현 (`/checklist-management`)
- [ ] 문서구분·업무구분 공통코드 상위코드 ID (`ASDOCSECD` 등)
- [ ] [미리보기] 다이얼로그 UI·데이터 소스
- [ ] `bchckUseYn` UI 반영 여부
- [ ] 영역 A·B 페이지네이션·검색 UI 노출 여부
- [ ] 권한(역할별 CRUD)
+385
View File
@@ -0,0 +1,385 @@
# 화면설계서: 코드관리
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 0.1 |
| 프로세스명 | UI설계 |
| 화면명 | 코드관리 |
| 화면 유형 | [v] 조회 [v] 입력 [v] 수정 [v] 삭제 [ ] 출력 |
| 메뉴 경로 | 디지털행정지원 > 사이트관리 > 코드관리 |
---
## 2. 화면 목적
설치인가 등 AS 업무에서 참조하는 **공종구분코드**(공종코드)와 **회계구분코드**(회계코드)를 한 화면에서 조회·등록·수정·삭제한다. 좌측 **공종코드 관리**와 우측 **회계코드 관리**는 독립된 목록·CRUD 영역이며, 각 섹션은 자체 API(`cnstp-se`, `acntg-se`)와 연동한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌───────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 페이지 타이틀: 코드관리 브레드크럼: 디지털행정지원 > 사이트관리 > 코드관리 │
├──────────────────────────────────────────────────┬────────────────────────────────────────────────┤
│ [영역 A] 공종코드 관리 │ [영역 B] 회계코드 관리 │
│ [등록] │ [등록] │
│ ┌────────────────────────────────────────────┐ │ ┌──────────────────────────────────────────┐ │
│ │ 코드 | 코드명 | 설명 | 사용여부 | 관리 │ │ │ 코드 | 코드명 | 설명 | 사용여부 | 관리 │ │
│ ├────────────────────────────────────────────┤ │ ├──────────────────────────────────────────┤ │
│ │ WC001 공공하수처리시설 … 사용 [수정][삭제]│ │ │ AC101 시설비 … 사용 [수정][삭제] │ │
│ │ WC002 하수관로정비 … 사용 [수정][삭제]│ │ │ AC102 용역비 … 사용 [수정][삭제] │ │
│ │ WC003 재이용시설 … 사용 [수정][삭제]│ │ │ AC103 운영비 … 미사용 [수정][삭제] │ │
│ └────────────────────────────────────────────┘ │ └──────────────────────────────────────────┘ │
│ « 1 2 3 … » │ « 1 2 3 … » │
└──────────────────────────────────────────────────┴────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입 — 좌·우 독립 조회
CALL cnstpSeMngService.findAll({ page, size })
DISPLAY 공종코드 그리드 (영역 A)
CALL acntgSeMngService.findAll({ page, size })
DISPLAY 회계코드 그리드 (영역 B)
// 영역 A: 공종코드 등록
ON 영역 A [등록] 버튼 CLICK DO
OPEN 공종코드 등록 다이얼로그
ON [저장] DO
VALIDATE cnstpSeCd
CALL cnstpSeMngService.save(CnstpSeMngSaveRequestDto)
CALL cnstpSeMngService.findAll
CLOSE 다이얼로그
END ON
ON [취소] / [닫기] DO
CLOSE 다이얼로그
END ON
END ON
// 영역 A: 공종코드 수정
ON 영역 A 행 [수정] CLICK DO
CALL cnstpSeMngService.findById(cnstpSeCd)
OPEN 공종코드 수정 다이얼로그 (기존 값 초기화)
ON [저장] DO
CALL cnstpSeMngService.update(cnstpSeCd, CnstpSeMngSaveRequestDto)
CALL cnstpSeMngService.findAll
CLOSE 다이얼로그
END ON
END ON
// 영역 A: 공종코드 삭제
ON 영역 A 행 [삭제] CLICK DO
CONFIRM "선택한 공종코드를 삭제하시겠습니까?"
CALL cnstpSeMngService.delete(cnstpSeCd)
CALL cnstpSeMngService.findAll
END ON
// 영역 B: 회계코드 등록
ON 영역 B [등록] 버튼 CLICK DO
OPEN 회계코드 등록 다이얼로그
ON [저장] DO
VALIDATE acntgSeCd, acntgSeCdNm
CALL acntgSeMngService.save(AcntgSeMngSaveRequestDto)
CALL acntgSeMngService.findAll
CLOSE 다이얼로그
END ON
ON [취소] / [닫기] DO
CLOSE 다이얼로그
END ON
END ON
// 영역 B: 회계코드 수정
ON 영역 B 행 [수정] CLICK DO
CALL acntgSeMngService.findById(acntgSeCd)
OPEN 회계코드 수정 다이얼로그 (기존 값 초기화)
ON [저장] DO
CALL acntgSeMngService.update(acntgSeCd, AcntgSeMngSaveRequestDto)
CALL acntgSeMngService.findAll
CLOSE 다이얼로그
END ON
END ON
// 영역 B: 회계코드 삭제
ON 영역 B 행 [삭제] CLICK DO
CONFIRM "선택한 회계코드를 삭제하시겠습니까?"
CALL acntgSeMngService.delete(acntgSeCd)
CALL acntgSeMngService.findAll
END ON
END
```
---
## 4. 영역 A: 공종코드 관리
백엔드 **공종구분코드**(`CnstpSeMng`) CRUD. 계층 구조(`upCnstpCd`, `cdLvNo`)를 지원하나, 본 화면은 **평면 목록**으로 표시.
### 4-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | ------------------------------------- |
| 섹션 타이틀 | 공종코드 관리 |
| 우측 버튼 | **[등록]** — 공종코드 등록 다이얼로그 |
### 4-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| -------- | ------------- | ------ | ------------------------ |
| 코드 | `cnstpSeCd` | string | PK, 최대 10자 |
| 코드명 | `cnstpSeCdNm` | string | 최대 100자 |
| 설명 | `expln` | string | 최대 1000자, 선택 |
| 사용여부 | `useYn` | string | `Y`→사용, `N`→미사용 |
| 관리 | — | — | 행별 **[수정] · [삭제]** |
- 행 클릭 시 별도 상세 이동 없음. 수정은 **[수정]** 버튼으로 다이얼로그 진입.
- 페이지네이션: Spring `Pageable` (`page`, `size`, `sort`) — **UI 노출**. 기본 `size: 10`, `@common/components` 공통 페이지네이션 컴포넌트 사용.
### 4-3. 버튼
| 버튼명 | 위치 | 동작 |
| ------ | -------------- | ---------------------------------------- |
| 등록 | 섹션 우측 상단 | 공종코드 등록 다이얼로그 오픈 |
| 수정 | 행별 | 상세 조회 후 수정 다이얼로그 (값 초기화) |
| 삭제 | 행별 | 확인 후 삭제, 목록 갱신 |
### 4-4. 다이얼로그: 공종코드 등록 / 수정
다이얼로그 입력 필드는 **코드 · 코드명 · 설명 · 사용여부** 4개만 노출한다.
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| -------- | --------- | ---- | ------ | ----------------- | ------------------------------- |
| 코드 | Input | Y | — | `cnstpSeCd` | 등록 시 입력, 수정 시 읽기 전용 |
| 코드명 | Input | N | — | `cnstpSeCdNm` | 최대 100자 |
| 설명 | Input | N | — | `expln` | 최대 1000자 |
| 사용여부 | Radio | N | Y | `useYn` | `Y`→사용, `N`→미사용 |
> UI에 노출하지 않는 API 필드(`upCnstpCd`, `cdLvNo`): 등록 시 `cdLvNo: 1` 등 기본값 전송, 수정 시 `findById` 응답값 유지.
| 버튼명 | 동작 |
| ------ | ------------------------------------------------ |
| 저장 | POST(등록) 또는 PUT(수정) 후 `findAll` 목록 갱신 |
| 취소 | 다이얼로그 닫기, 입력값 폐기 |
| 닫기 | 다이얼로그 닫기 |
---
## 5. 영역 B: 회계코드 관리
백엔드 **회계구분관리**(`AcntgSeMng`) CRUD. 영역 A와 동일한 **평면 목록** UI 패턴을 따른다.
### 5-1. 헤더 / 툴바
| 항목 | 내용 |
| ----------- | ------------------------------------- |
| 섹션 타이틀 | 회계코드 관리 |
| 우측 버튼 | **[등록]** — 회계코드 등록 다이얼로그 |
### 5-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| -------- | ------------- | ------ | ------------------------ |
| 코드 | `acntgSeCd` | string | PK, 최대 10자 |
| 코드명 | `acntgSeCdNm` | string | 최대 100자 |
| 설명 | `expln` | string | 최대 1000자, 선택 |
| 사용여부 | `useYn` | string | `Y`→사용, `N`→미사용 |
| 관리 | — | — | 행별 **[수정] · [삭제]** |
- 페이지네이션: 영역 A와 동일 — **UI 노출**, 기본 `size: 10`.
### 5-3. 버튼
| 버튼명 | 위치 | 동작 |
| ------ | -------------- | ---------------------------------------- |
| 등록 | 섹션 우측 상단 | 회계코드 등록 다이얼로그 오픈 |
| 수정 | 행별 | 상세 조회 후 수정 다이얼로그 (값 초기화) |
| 삭제 | 행별 | 확인 후 삭제, 목록 갱신 |
### 5-4. 다이얼로그: 회계코드 등록 / 수정
다이얼로그 입력 필드는 **코드 · 코드명 · 설명 · 사용여부** 4개만 노출한다. (영역 A와 동일 패턴)
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| -------- | --------- | ---- | ------ | ----------------- | ------------------------------- |
| 코드 | Input | Y | — | `acntgSeCd` | 등록 시 입력, 수정 시 읽기 전용 |
| 코드명 | Input | Y | — | `acntgSeCdNm` | 최대 100자 |
| 설명 | Input | N | — | `expln` | 최대 1000자 |
| 사용여부 | Radio | N | Y | `useYn` | `Y`→사용, `N`→미사용 |
> UI에 노출하지 않는 API 필드(`upAcntgSeCd`, `cdLvNo`): 등록 시 `cdLvNo: 1` 등 기본값 전송, 수정 시 `findById` 응답값 유지.
| 버튼명 | 동작 |
| ------ | ------------------------------------------------ |
| 저장 | POST(등록) 또는 PUT(수정) 후 `findAll` 목록 갱신 |
| 취소 | 다이얼로그 닫기, 입력값 폐기 |
| 닫기 | 다이얼로그 닫기 |
---
## 6. API/DTO 명세
### 6-1. 공통
| 항목 | 내용 |
| ----------- | ---------------------------------------------------------------------------------- |
| 서비스 | `as-service` |
| Base prefix | `/as-service/api/v1` (`ENDPOINT.AS_COMMON_SERVICE`) |
| 인증 | JWT (`Authorization` 헤더), `X-Site-Id`, `token-id` |
| 페이징 | Spring `Pageable`: `page`, `size`, `sort` → `PageResponse<T>` |
| 엔티티 | `@/entities/cnstp` (`cnstpSeMngService`), `@/entities/acntg` (`acntgSeMngService`) |
**부가 API (본 화면 목록 외 참조용)**
| 리소스 | Method | URL | Response | 용도 |
| ------------ | ------ | --------------------- | --------------------- | --------------------------------- |
| 공종구분코드 | GET | `/cnstp-se/list/tree` | `CnstpSeMngTreeDto[]` | 계층 트리 (타 화면·상위코드 선택) |
| 회계구분관리 | GET | `/acntg-se/list/tree` | `AcntgSeMngTreeDto[]` | 계층 트리 (타 화면·상위코드 선택) |
### 6-2. 공종구분코드 (영역 A) — `cnstp-se`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| --------- | ------ | ----------------------- | -------------------------------- | ----------------------------- | ---------------------------- |
| 목록 조회 | GET | `/cnstp-se` | `CnstpSeMngSearchDto` + Pageable | `Page<CnstpSeMngResponseDto>` | `cnstpSeMngService.findAll` |
| 트리 조회 | GET | `/cnstp-se/list/tree` | — | `CnstpSeMngTreeDto[]` | `cnstpSeMngService.findTree` |
| 상세 조회 | GET | `/cnstp-se/{cnstpSeCd}` | — | `CnstpSeMngResponseDto` | `cnstpSeMngService.findById` |
| 등록 | POST | `/cnstp-se` | `CnstpSeMngSaveRequestDto` | `string` (cnstpSeCd) | `cnstpSeMngService.save` |
| 수정 | PUT | `/cnstp-se/{cnstpSeCd}` | `CnstpSeMngSaveRequestDto` | `string` (cnstpSeCd) | `cnstpSeMngService.update` |
| 삭제 | DELETE | `/cnstp-se/{cnstpSeCd}` | — | `string` (cnstpSeCd) | `cnstpSeMngService.delete` |
**CnstpSeMngResponseDto**
| Field | 타입 | 설명 |
| ------------- | ------- | ------------------ |
| `cnstpSeCd` | string | 공종구분코드 |
| `cnstpSeCdNm` | string | 공종구분코드명 |
| `upCnstpCd` | string? | 상위공종코드 |
| `cdLvNo` | number | 코드레벨번호 |
| `useYn` | string | 사용여부 |
| `expln` | string? | 설명 (최대 1000자) |
| `rgtr` | string? | 등록자 UUID |
| `rgtrNm` | string? | 등록자명 |
| `mdfr` | string? | 수정자 UUID |
| `regDt` | string? | 등록일시 |
| `mdfcnDt` | string? | 수정일시 |
**CnstpSeMngSaveRequestDto**: `cnstpSeCd`, `cnstpSeCdNm?`, `upCnstpCd?`, `cdLvNo?`, `useYn?`, `expln?`
**CnstpSeMngSearchDto**: `cnstpSeCd?`, `cnstpSeCdNm?`, `useYn?` + `page`, `size`, `sort`
**React Query hooks**: `useCnstpSeMngPage`, `useCnstpSeMngTree`, `useCnstpSeMng`, `useSaveCnstpSeMng`, `useUpdateCnstpSeMng`, `useDeleteCnstpSeMng`
### 6-3. 회계구분관리 (영역 B) — `acntg-se`
| 기능 | Method | URL | Request / Query | Response | 클라이언트 |
| --------- | ------ | ----------------------- | -------------------------------- | ----------------------------- | ---------------------------- |
| 목록 조회 | GET | `/acntg-se` | `AcntgSeMngSearchDto` + Pageable | `Page<AcntgSeMngResponseDto>` | `acntgSeMngService.findAll` |
| 트리 조회 | GET | `/acntg-se/list/tree` | — | `AcntgSeMngTreeDto[]` | `acntgSeMngService.findTree` |
| 상세 조회 | GET | `/acntg-se/{acntgSeCd}` | — | `AcntgSeMngResponseDto` | `acntgSeMngService.findById` |
| 등록 | POST | `/acntg-se` | `AcntgSeMngSaveRequestDto` | `string` (acntgSeCd) | `acntgSeMngService.save` |
| 수정 | PUT | `/acntg-se/{acntgSeCd}` | `AcntgSeMngSaveRequestDto` | `string` (acntgSeCd) | `acntgSeMngService.update` |
| 삭제 | DELETE | `/acntg-se/{acntgSeCd}` | — | `string` (acntgSeCd) | `acntgSeMngService.delete` |
**AcntgSeMngResponseDto**
| Field | 타입 | 설명 |
| ------------- | ------- | ------------------ |
| `acntgSeCd` | string | 회계구분코드 |
| `acntgSeCdNm` | string | 회계구분코드명 |
| `upAcntgSeCd` | string? | 상위회계구분코드 |
| `cdLvNo` | number | 코드레벨번호 |
| `useYn` | string | 사용여부 |
| `expln` | string? | 설명 (최대 1000자) |
| `rgtr` | string? | 등록자 UUID |
| `rgtrNm` | string? | 등록자명 |
| `mdfr` | string? | 수정자 UUID |
| `regDt` | string? | 등록일시 |
| `mdfcnDt` | string? | 수정일시 |
**AcntgSeMngSaveRequestDto**: `acntgSeCd`, `acntgSeCdNm`, `upAcntgSeCd?`, `cdLvNo`, `useYn?`, `expln?`
**AcntgSeMngSearchDto**: `acntgSeCd?`, `acntgSeCdNm?`, `useYn?` + `page`, `size`, `sort`
**React Query hooks**: `useAcntgSeMngPage`, `useAcntgSeMngTree`, `useAcntgSeMng`, `useSaveAcntgSeMng`, `useUpdateAcntgSeMng`, `useDeleteAcntgSeMng`
---
## 7. UI 구현 가이드
### 7-1. 기술 스택·원칙
- Next.js App Router, React Query (`@tanstack/react-query`)
- `@common/components` — `Card`, `Button`, `Dialog`, `DataTable` / `useDataTable`
- `@tanstack/react-table` — 컬럼 정의는 `model/*-table-columns.tsx` 분리
- 기존 entity 레이어 재사용: `@/entities/cnstp`, `@/entities/acntg` (service·queries·interface 구현 완료)
- 좌·우 2열 레이아웃: `grid grid-cols-1 lg:grid-cols-2 gap-4` (모바일 1열 스택)
- 삭제 전 `confirm` / Toast 피드백 — 프로젝트 공통 패턴 준수
- 목록 하단 **페이지네이션 UI 노출** — 영역 A·B 각각 독립 page state
- **검색 필터 UI 미제공** — `findAll` 호출 시 `page`, `size`만 전달
- **등록/수정 다이얼로그** — 코드·코드명·설명은 `Input`, 사용여부는 `Radio` (4필드만 노출)
- 상위 코드 삭제 시 **하위 코드 연쇄 삭제 UI·로직 미구현** — 단건 삭제만 수행
### 7-2. 모듈·파일 배치
| 구분 | 경로 (제안) |
| ----------------- | -------------------------------------------------------------------- |
| 라우트 | `apps/as/src/app/(authenticated)/site/code-management/page.tsx` |
| 페이지 컴포넌트 | `apps/as/src/pages/code-management/ui/code-management-page.tsx` |
| 공종 그리드 컬럼 | `apps/as/src/pages/code-management/model/cnstp-se-table-columns.tsx` |
| 회계 그리드 컬럼 | `apps/as/src/pages/code-management/model/acntg-se-table-columns.tsx` |
| 공종 등록/수정 폼 | `apps/as/src/pages/code-management/ui/cnstp-se-form-dialog.tsx` |
| 회계 등록/수정 폼 | `apps/as/src/pages/code-management/ui/acntg-se-form-dialog.tsx` |
| API·타입 (기존) | `@/entities/cnstp`, `@/entities/acntg` |
### 7-3. 영역별 UI 매핑
| 영역 | UI 컴포넌트 | 데이터 소스 | 비고 |
| ------------ | ------------------------------------- | ------------------------------------------- | ------------------------------ |
| A | `CnstpSeSection` + Table + Pagination | `useCnstpSeMngPage({ page, size })` | 독립 page state·mutation |
| B | `AcntgSeSection` + Table + Pagination | `useAcntgSeMngPage({ page, size })` | A와 상호 의존 없음 |
| A 다이얼로그 | `CnstpSeFormDialog` | `useCnstpSeMng`, `useSave/UpdateCnstpSeMng` | Input 4필드 + Radio 사용여부 |
| B 다이얼로그 | `AcntgSeFormDialog` | `useAcntgSeMng`, `useSave/UpdateAcntgSeMng` | Input 4필드 + Radio 사용여부 |
### 7-4. 다이얼로그·라우팅
| 다이얼로그 | 트리거 | 라우팅 |
| ------------- | ---------------- | -------------------- |
| 공종코드 등록 | 영역 A [등록] | URL 변경 없음 (모달) |
| 공종코드 수정 | 영역 A 행 [수정] | URL 변경 없음 (모달) |
| 회계코드 등록 | 영역 B [등록] | URL 변경 없음 (모달) |
| 회계코드 수정 | 영역 B 행 [수정] | URL 변경 없음 (모달) |
### 7-5. 데이터 흐름 (구현 관점)
```
BEGIN
// 페이지 마운트 — 검색 조건 없이 page·size만 전달
const cnstpQuery = useCnstpSeMngPage({ page: 0, size: 20 })
const acntgQuery = useAcntgSeMngPage({ page: 0, size: 20 })
DISPLAY CodeManagementPage
├─ CnstpSeSection(data=cnstpQuery.data, pagination)
└─ AcntgSeSection(data=acntgQuery.data, pagination)
// 페이지 변경
ON pagination page/size CHANGE DO
refetch cnstpQuery | acntgQuery with new page, size
END ON
// mutation 성공 시
ON save/update/delete SUCCESS DO
invalidateQueries cnstp-se | acntg-se baseKey
TOAST "저장되었습니다." | "삭제되었습니다."
END ON
END
```
+498
View File
@@ -0,0 +1,498 @@
# 화면설계서: 전국 행정지원 지도 (대시보드 메인)
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 1.2 |
| 프로세스명 | UI설계 |
| 화면명 | 전국 행정지원 지도 (대시보드 메인) |
| 화면 ID | TBD |
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
| 메뉴 경로 | 메인 |
---
## 2. 화면 목적
로그인 사용자의 **기관 권한·당해 연도** 기준으로 문서별 진행상태 통계와 행정지원 알림을 한 화면에서 조회한다. 좌측 지도에서 전국 행정지원 현황을 보고, 우측에서 진행상황 카드·알림이력·공지사항을 확인한 뒤 알림 [처리하기]로 연계 업무 화면으로 이동한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
브레드크럼·페이지 타이틀은 스크린샷에 없음. 홈(`/`) 메인 콘텐츠로 구성한다.
```
┌────────────────────────────────┬─────────────────────────────────────────────────┐
│ [영역 A] 전국 행정지원 지도 │ [영역 B] 문서별 진행상황 카드 (2×2) │
│ 타이틀: 전국 행정지원 지도 │ ┌────────────────┐ ┌────────────────┐ │
│ │ │ 재원협의 │ │ 설치인가 │ │
│ (대한민국 시도·시군구 경계) │ │ 신청/진행/완료 │ │ 신청/진행/완료 │ │
│ 마커: 서울 강남구, │ ├────────────────┤ ├────────────────┤ │
│ 대전 유성구, │ │ 설계자문 │ │ 기본계획 │ │
│ 부산 해운대구 │ │ 신청/진행/완료 │ │ 신청/진행/완료 │ │
│ │ └────────────────┘ └────────────────┘ │
│ ├─────────────────────────────────────────────────┤
│ │ [영역 C] 알림이력 │
│ │ 알림이력 업데이트 HH:mm [전체보기] │
│ │ [오늘 N건] [지연 N건] [완료대기 N건] │
│ │ [뱃지] [일자] 제목 [처리하기] │
│ │ … (최대 5건) │
│ ├─────────────────────────────────────────────────┤
│ │ [영역 D] 공지사항 [더보기] │
│ │ ┌────┬────────┬────────┬────────┬──────┐ │
│ │ │번호│ 제목 │ 작성자 │ 작성일 │조회수│ │
│ │ └────┴────────┴────────┴────────┴──────┘ │
└────────────────────────────────┴─────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
// searchYear 미전달 → 서버가 당해 연도로 고정. 알림 목록은 최신 5건
CALL getMainDashboard(DashboardMainSearchDto) // Authorization은 client 인터셉터
DISPLAY 진행상황 카드 (영역 B) // docPrgrsSttsList, TOTAL 행 제외
DISPLAY 알림 통계 칩 · 알림 목록 (영역 C) // notiStats, notiList
CALL getUserPostList(UserPostSearchParams) // board-service, AS_DASHBOARD_BBS_ID
DISPLAY 공지사항 그리드 (영역 D)
CALL 지도 마커/음영 조회(…) // TBD — 대시보드 API에 없음
DISPLAY 전국 지도 (영역 A)
ON 영역 C [전체보기] 버튼 CLICK DO
NAVIGATE /nti-hstry
END ON
ON 영역 C [처리하기] 버튼 CLICK DO
CALL readNoti(notiId) // entities/noti
NAVIGATE notiUrl // 비어 있으면 이동하지 않음
CALL getMainDashboard(…) // → 영역 C 갱신 (invalidate)
END ON
ON 영역 D [더보기] 버튼 CLICK DO
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID} // as 앱 공지 목록 라우트 미구현. st/cm 관례
END ON
ON 영역 D 행 CLICK DO
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}
END ON
ON 영역 B 카드 CLICK DO
NAVIGATE 해당 문서 목록 // §5-3
END ON
ON 영역 A 마커 CLICK DO
// TBD — 팝업/필터/이동 여부 미확정
END ON
END
```
---
## 4. 영역 A — 전국 행정지원 지도
스크린샷 타이틀: **전국 행정지원 지도**.
`getMainDashboard` 응답에 지도·좌표·행정구역 음영 데이터는 **없다**. 마커·음영 소스 API는 **TBD**.
### 4-1. 표시 요소
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| ----------- | --------- | ---- | ------ | ----------------- | ----------------------------------------------------------------- |
| 섹션 타이틀 | Text | — | — | — | 고정 문구 `전국 행정지원 지도` |
| 전국 지도 | Map | — | — | TBD | 시도·시군구 경계. 북·동부 음영(청) / 남서부 비음영(백) — 의미 TBD |
| 마커 라벨 | Marker | — | — | TBD | 스크린샷 샘플: `서울 강남구`, `대전 유성구`, `부산 해운대구` |
### 4-2. 상호작용
| 동작 | 설명 |
| ---------- | ----------------------------------------- |
| 마커 클릭 | TBD (상세 팝업 / 해당 지자체 필터 / 이동) |
| 지도 줌·팬 | TBD (스크린샷에 컨트롤 없음) |
`entities/adstdstr`는 시도·구군 **콤보**만 제공하며 좌표를 주지 않는다. 지도 라이브러리도 as 앱에 없음 — 선정 TBD.
---
## 5. 영역 B — 문서별 진행상황 카드
`DashboardMainResponseDto.docPrgrsSttsList`를 **`docSeComCd`로 매핑**해 4장 카드를 그린다. 배열 순서에 의존하지 않는다.
스크린샷 배치(좌→우, 상→하):
| 화면 카드 타이틀 | `docSeComCd` | API `docSeNm` |
| ----------------- | ------------ | -------------- |
| 재원협의 진행상황 | `FNRS` | 재원협의 |
| 설치인가 진행상황 | `INSTL` | 설치인가 |
| 설계자문 진행상황 | `CNFM` | 설계자문 |
| 기본계획 진행상황 | `SEWER` | 하수도기본계획 |
서버 SQL `ORDER BY`는 `INSTL → FNRS → SEWER → CNFM` 이며, `GROUP BY ROLLUP`으로 **`docSeComCd = TOTAL`** 합계 행이 추가된다. 카드에는 TOTAL 행을 **표시하지 않는다**.
카드 타이틀은 스크린샷 문구(`… 진행상황`)를 쓴다. `SEWER`의 API명 `하수도기본계획`은 화면에서 `기본계획`으로 축약한다.
### 5-1. 카드 내부 필드
각 카드 공통 구조. `totCnt`는 화면에 없음.
| 표시명 | Field | 비고 |
| ------ | ---------- | ------------------ |
| 신청 | `reqCnt` | 파란 아이콘 + 건수 |
| 진행 | `prgrsCnt` | 초록 아이콘 + 건수 |
| 완료 | `cmptnCnt` | 주황 아이콘 + 건수 |
상태 그룹(서버): 임시저장(`SV`/`CV`) 제외. 문서별 `prgrs_stts_com_cd` → `REQ`/`PRG`/`CMP`.
### 5-2. 검색 파라미터 (화면 미표시)
영역 B에 연도·필터 UI는 없다. 서버 기본값을 따른다.
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| ------ | --------- | ---- | --------- | ----------------- | --------------------------------------------------------------------- |
| (없음) | — | — | 당해 연도 | `searchYear` | 미전달 시 서버 `LocalDate.now().getYear()`. 연도 셀렉터 추가 여부 TBD |
`docSeComCd` 쿼리는 **알림 목록 필터**에만 쓰이며, 진행상황 SQL에는 전달되지 않는다. 카드 4종은 항상 전원 집계한다.
### 5-3. 상호작용
카드 클릭 시 **해당 문서 목록 페이지**로 이동한다. `docSeComCd` → 라우트 고정 매핑.
| 동작 | `docSeComCd` | 이동 경로 |
| --------- | ------------ | ----------------- |
| 카드 클릭 | `FNRS` | `/fnrs-cnsltn` |
| 카드 클릭 | `INSTL` | `/instl-sncn` |
| 카드 클릭 | `CNFM` | `/design-cnstn` |
| 카드 클릭 | `SEWER` | `/sewer-bsc-plan` |
---
## 6. 영역 C — 알림이력
서버가 cms-service 알림을 대시보드 응답에 합친다. 목록은 **page=0, size=5** (최신 5건). 페이지네이션 없음.
### 6-1. 헤더
| 표시명 | Field / 소스 | 비고 |
| ------------- | ------------ | --------------------------------------------------------------------------------------- |
| 섹션 타이틀 | — | 고정 문구 `알림이력` |
| 업데이트 시각 | (client) | 스크린샷 `업데이트 10:32`. API 필드 없음. `getMainDashboard` 마지막 fetch 시각(`HH:mm`) |
| 전체보기 | Button | `/nti-hstry`로 이동 |
### 6-2. 통계 칩
`NotiAsStatResponseDto`.
| 표시명 | Field | 비고 |
| ------------ | -------------- | -------------------------------------- |
| 오늘 N건 | `todayCount` | 당일 00:00 이후 수신 |
| 지연 N건 | `delayCount` | `notReadYn=N` 이고 수신 후 3일 이상 |
| 완료대기 N건 | `pendingCount` | `notReadYn=Y` 이고 `notiPrcsCmptnYn=N` |
### 6-3. 목록 행
| 표시명 | Field | 비고 |
| ------ | --------- | --------------------------------------------- |
| 일자 | `regDt` | 표시 `YYYY-MM-DD`. 응답 `yyyy-MM-dd HH:mm:ss` |
| 제목 | `notiTtl` | 알림 제목 |
| 처리 | (action) | 스크린샷 라벨 `처리하기`. 클릭 §6-4 |
응답에 있으나 이 목록에 **표시하지 않음**:
| Field | 비고 |
| ----------------- | ----------------------------------------- |
| `notiId` | 행 키 · `readNoti` 파라미터 |
| `recvUnqId` | 수신 회원 UUID (서버 로그인 사용자 고정) |
| `senderUnqId` | 발신 회원 UUID |
| `notiType` | `ADSS` |
| `notiCn` | 본문 |
| `notiUrl` | 처리 후 이동 URL. 컬럼 미표시 |
| `notiLnkgUnqNo` | 알림연결고유번호 |
| `docSeComCd` | 문서구분 코드 |
| `docSeComNm` | 문서구분명 (알림이력 화면에서는 연계업무) |
| `notReadYn` | 뱃지 판정 TBD에 사용 가능 |
| `notiIdntyYn` | 확인 여부 |
| `notiIdntyDt` | 확인 일시 |
| `notiPrcsCmptnYn` | 처리 완료 여부 |
| `notiPrcsCmptnDt` | 처리 완료 일시 |
### 6-4. 행 상호작용
| 동작 | 설명 |
| --------------- | ----------------------------------------------------------------------------------------------------------------- |
| [전체보기] 클릭 | `/nti-hstry` (알림이력 목록)로 이동 |
| [처리하기] 클릭 | `readNoti(notiId)` 후 `notiUrl`로 이동. URL이 비면 이동하지 않음(읽음 처리는 수행). `dashboardBaseKey` invalidate |
`DashboardController`에 읽음 API는 없다. **`entities/noti`의 `readNoti`** 를 쓴다. 알림이력 화면과 동일 (`PUT /cms-service/api/v1/noti/{notiId}/read`).
### 6-5. 검색 파라미터 (화면 미표시)
서버 `toNotiSearchDto()` 기본: `notiType = ADSS`. 화면 필터 없음.
| DashboardMainSearchDto | 화면 | 비고 |
| ---------------------- | ---- | --------------------------------- |
| `notiType` | 없음 | 서버 기본 `ADSS` |
| `notReadYn` | 없음 | 통계 칩 클릭 필터 시 후보 (TBD) |
| `startDt` / `endDt` | 없음 | 대시보드 기본 미전달 |
| `docSeComCd` | 없음 | 알림만 필터. 진행상황 카드와 무관 |
---
## 7. 영역 D — 공지사항
`getMainDashboard`에 공지 필드는 없다. **board-service** `getUserPostList`로 별도 조회한다.
- Base: `GET /board-service/api/v1/posts/user/{AS_DASHBOARD_BBS_ID}`
- `AS_DASHBOARD_BBS_ID` = `NEXT_PUBLIC_AS_DASHBOARD_BBS_ID` (로컬 예: `7`)
- 클라이언트: `@/entities/board` — `postApi.getUserPostList` / `useUserPostList`
페이지네이션 컨트롤은 없다. 요약 목록만 표시한다 (`page=0`, `size=3`).
### 7-1. 헤더·버튼
| 버튼명 | 동작 |
| ------ | ------------------------------------------------------------------------------------------- |
| 더보기 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}` 로 이동. as 앱에 공지 목록 라우트는 없음(st/cm 관례) |
### 7-2. 그리드 컬럼
스크린샷 헤더 순서. `UserPost`.
| 표시명 | Field | 비고 |
| ------ | --------- | ---------------------------------------------------- |
| 번호 | `rowNo` | `upndFixYn` = `Y` 이면 `[공지]` Badge, 그 외 `rowNo` |
| 제목 | `pstTtl` | truncate |
| 작성자 | `wrtrNm` | |
| 작성일 | `regDt` | 표시 `YYYY-MM-DD` (응답 datetime) |
| 조회수 | `inqNocs` | 숫자 |
조회 파라미터 (`UserPostSearchParams`):
| 필드 | 값 | 비고 |
| ------------- | ----------- | ------------------------------------------ |
| `page` | `0` | Spring 0-based |
| `size` | `3` | 대시보드 요약. 스크린샷 3행 |
| `keywordType` | `bbsTypeCd` | st 대시보드 공지 패널과 동일 |
| `keyword` | `BBSNOTICE` | 게시판 유형. path의 bbsId와 함께 공지 한정 |
응답에 있으나 그리드에 **표시하지 않음**: `bbsId`, `pstNo`(행 키·상세 이동), `pstCn`, `upndFixYn`(번호 열 판정만), `rgtrNm`, `atflCd` 등.
### 7-3. 행 상호작용
| 동작 | 설명 |
| ------- | ------------------------------------------------------------------------------ |
| 행 클릭 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}` (as 앱 공지 상세 라우트 미구현) |
---
## 8. API/DTO 명세
### 8-1. 공통
| 항목 | 값 |
| --------------- | ---------------------------------------------------------------------------------------------- |
| Base prefix | `/as-service/api/v1/dashboard` (`ENDPOINT.AS_SERVICE.DASHBOARD`) |
| 인증 | `Authorization` 헤더. 프론트는 `client` 인터셉터. 로그인 기관 권한은 서버가 사용자 캐시로 적용 |
| 클라이언트 모듈 | `@/entities/dashboard` — `dashboardService` / `useGetMainDashboard` |
| 알림 읽음 | `@/entities/noti` — `notiService.readNoti` / `useReadNoti` |
| 공지사항 | `@/entities/board` — `postApi.getUserPostList` / `useUserPostList` |
영역 A(지도)는 본 컨트롤러·board-service 범위 밖 → **TBD**.
### 8-2. 대시보드 메인 조회 — `getMainDashboard`
| 기능 | Method | URL | Request DTO | Response DTO | API |
| -------------------------------------------- | ------ | ------------- | ------------------------------- | -------------------------- | ----------------------------------------------------------- |
| 대시보드 메인 통합진행상태 및 알림 정보 조회 | GET | `{BASE}/main` | Query: `DashboardMainSearchDto` | `DashboardMainResponseDto` | `dashboardService.getMainDashboard` / `useGetMainDashboard` |
**Request (`DashboardMainSearchDto`)**
| 필드 | 필수 | 설명 |
| ------------ | ---- | ---------------------------------------------------------------------- |
| `searchYear` | N | 검색 연도. 미전달 시 서버 당해 연도. 영역 B 집계 기간 |
| `notiType` | N | 알림 종류. 미전달 시 서버 `ADSS` |
| `notReadYn` | N | 알림 확인 여부 `Y`/`N`. 본 화면 미사용 |
| `startDt` | N | 알림 시작일 `YYYY-MM-DD`. 본 화면 미사용 |
| `endDt` | N | 알림 종료일 `YYYY-MM-DD`. 본 화면 미사용 |
| `docSeComCd` | N | 알림 문서구분 (`INSTL`/`FNRS`/`SEWER`/`CNFM` 등). 진행상황 집계와 무관 |
`Authorization`은 service 시그니처에 넣지 않는다.
**Response (`DashboardMainResponseDto`)**
| 필드 | 타입 | 설명 |
| ------------------ | --------------------------- | --------------------------- |
| `docPrgrsSttsList` | `DocPrgrsSttsResponseDto[]` | 문서별 진행상태 + TOTAL 행 |
| `notiList` | `NotiAsUserResponseDto[]` | 최신 최대 5건 |
| `notiStats` | `NotiAsStatResponseDto` | 오늘 / 지연 / 완료대기 건수 |
**`DocPrgrsSttsResponseDto`**
| 필드 | 타입 | 설명 |
| ------------ | ------ | --------------------------------------------- |
| `docSeNm` | string | 문서 구분 명칭 |
| `docSeComCd` | string | `INSTL` / `FNRS` / `SEWER` / `CNFM` / `TOTAL` |
| `reqCnt` | number | 신청 건수 |
| `prgrsCnt` | number | 진행 건수 |
| `cmptnCnt` | number | 완료 건수 |
| `totCnt` | number | 총 건수 (화면 미표시) |
**`NotiAsStatResponseDto`**
| 필드 | 타입 | 설명 |
| -------------- | ------ | --------- |
| `todayCount` | number | 오늘 알림 |
| `delayCount` | number | 지연 알림 |
| `pendingCount` | number | 완료대기 |
**`NotiAsUserResponseDto`** — 필드 상세는 `docs/spec/nti-hstry` §6-2와 동일 (`notiId`, `notiTtl`, `regDt`, `notiUrl`, `notReadYn`, `notiPrcsCmptnYn` 등).
### 8-3. 알림 읽음 처리 — `readNoti`
대시보드 컨트롤러가 아니라 cms-service. 영역 C [처리하기]에서 호출.
| 기능 | Method | URL | Request DTO | Response DTO | API |
| -------------- | ------ | ---------------------------------------- | ----------------------- | ---------------- | -------------------------------------- |
| 알림 읽음 처리 | PUT | `/cms-service/api/v1/noti/{notiId}/read` | Path: `notiId` (`Long`) | `Long` (알림 ID) | `notiService.readNoti` / `useReadNoti` |
Body 없음. 성공 시 `dashboardBaseKey`(및 필요 시 `notiBaseKey`) invalidate.
### 8-4. 전국 행정지원 지도 — TBD
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ------------------- | ------ | --- | ----------- | ------------ | --- |
| 지도 마커·음영 조회 | TBD | TBD | TBD | TBD | TBD |
### 8-5. 공지사항 — `getUserPostList`
board-service. bbsId는 환경변수 `AS_DASHBOARD_BBS_ID`로 path에 고정한다.
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ----------------------- | ------ | ------------------------------------------ | ----------------------------- | ------------------------ | --------------------------------------------- |
| 대시보드 공지 요약 목록 | GET | `/board-service/api/v1/posts/user/{bbsId}` | Query: `UserPostSearchParams` | `PageResponse<UserPost>` | `postApi.getUserPostList` / `useUserPostList` |
**Request (`UserPostSearchParams`)** — 본 화면 고정값
| 필드 | 필수 | 설명 |
| ------------- | ---- | -------------- |
| `page` | N | `0` |
| `size` | N | `3` |
| `keywordType` | N | `bbsTypeCd` |
| `keyword` | N | `BBSNOTICE` |
| `popupYn` 등 | N | 본 화면 미사용 |
**Response (`UserPost`) — 화면 매핑 필드**
| 필드 | 타입 | 설명 |
| ----------- | ------ | --------------------------- |
| `rowNo` | number | 번호. 고정글이 아니면 표시 |
| `pstNo` | number | 게시 번호. 상세 이동 키 |
| `bbsId` | number | 게시판 ID |
| `pstTtl` | string | 제목 |
| `wrtrNm` | string | 작성자 |
| `regDt` | string | 작성일 |
| `inqNocs` | number | 조회수 |
| `upndFixYn` | string | 상단고정 `Y`/`N` → `[공지]` |
---
## 9. UI 구현 가이드
### 9-1. 기술 스택·원칙
- 라우트: 기존 홈 `app/(main)/(with-base-layout)/page.tsx` → `pages/home` (현재 `MainPage` 플레이스홀더)
- UI: `@common/components` — Card, Badge, Button, Table(공지). 지도 라이브러리 TBD
- 데이터: React Query `useGetMainDashboard()`. 처리하기는 `useReadNoti()`. 공지는 `useUserPostList()`
- 진행상황 카드: `docSeComCd`로 4슬롯 매핑. `TOTAL` 제외
- 알림 목록: 테이블이 아닌 행 리스트(뱃지 + 일자 + 제목 + 버튼)
- 공지: `@tanstack/react-table` + `useDataTable`, 페이지네이션 없음
- 검색 조건 URL 동기화: 본 화면 필터 없음. `searchYear` UI가 생기면 그때 검토
### 9-2. 모듈·파일 배치
| 구분 | 경로 |
| ------------- | ------------------------------------------------------------ |
| 라우트 | `app/(main)/(with-base-layout)/page.tsx` |
| 페이지 | `pages/dashboard/ui/dashboard-page.tsx` |
| 지도 | `pages/dashboard/ui/national-as-map.tsx` (API TBD, Skeleton) |
| 진행상황 카드 | `pages/dashboard/ui/doc-prgrs-stts-cards.tsx` |
| 알림이력 | `pages/dashboard/ui/dashboard-noti-list.tsx` |
| 공지사항 | `pages/dashboard/ui/dashboard-notice-table.tsx` |
| 공지 컬럼 | `pages/dashboard/model/dashboard-notice-table-columns.tsx` |
| Entity API | `entities/dashboard/api/dashboard-service.ts` |
| Entity Hook | `entities/dashboard/api/queries.ts` → `useGetMainDashboard` |
| Entity Model | `entities/dashboard/model/dashboard-interface.ts` |
| 알림 읽음 | `entities/noti` → `useReadNoti` |
| 공지 Entity | `entities/board` → `useUserPostList` |
Entity(`getMainDashboard`, `getUserPostList`) 구현됨. 영역 B·C·D 화면 구현. 영역 A는 Skeleton.
### 9-3. 영역별 UI 매핑
| 영역 | 컴포넌트 | 데이터 |
| ---- | ------------------------ | ---------------------------------------------------------------------------- |
| A | `national-as-map` | Skeleton (지도 API TBD) |
| B | `doc-prgrs-stts-cards` | `useGetMainDashboard` → `docPrgrsSttsList` (`TOTAL` 제외, 코드 매핑) |
| C | `dashboard-noti-list` | 동 훅 → `notiStats`, `notiList`. 처리 → `useReadNoti`. 전체보기 `/nti-hstry` |
| D | `dashboard-notice-table` | `useUserPostList` → `content[]`. [더보기]·행 클릭 → `/cms/bbsnotice/...` |
### 9-4. 다이얼로그·라우팅
| 항목 | 내용 |
| -------------- | -------------------------------------------------------------------------------------- |
| 다이얼로그 | 없음 |
| 본 화면 라우트 | `/` (홈) |
| 알림 처리 이동 | [처리하기] → `readNoti` 후 `notiUrl` (알림이력과 동일, SEWER URL은 상세 세그먼트 제거) |
| 알림 전체보기 | `/nti-hstry` |
| 공지 더보기 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}` (as 앱 공지 라우트 미구현, st/cm 관례) |
| 공지 행 클릭 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}` |
| 진행상황 카드 | §5-3 목록 라우트 (`/fnrs-cnsltn`, `/instl-sncn`, `/design-cnstn`, `/sewer-bsc-plan`) |
### 9-5. 데이터 흐름
```
BEGIN
// 최초 진입 (홈)
CALL getMainDashboard({})
DISPLAY 영역 B // docPrgrsSttsList — FNRS/INSTL/CNFM/SEWER 슬롯
DISPLAY 영역 C // notiStats + notiList(≤5)
CALL getUserPostList({ page: 0, size: 3, keywordType: "bbsTypeCd", keyword: "BBSNOTICE" })
DISPLAY 영역 D
CALL 지도 데이터 // TBD → 영역 A
ON 영역 C [전체보기] CLICK DO
NAVIGATE /nti-hstry
END ON
ON 영역 C [처리하기] CLICK DO
CALL readNoti(notiId)
IF notiUrl THEN
NAVIGATE notiUrl
END IF
CALL getMainDashboard // invalidate dashboardBaseKey
END ON
ON 영역 B 카드 CLICK DO
NAVIGATE 문서 목록 // §5-3
END ON
ON 영역 D [더보기] CLICK DO
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}
END ON
ON 영역 D 행 CLICK DO
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}
END ON
END
```
@@ -0,0 +1,284 @@
# 화면설계서: 필수첨부파일관리
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | ---------------------------------------------- |
| 버전 | 0.1 |
| 프로세스명 | UI설계 |
| 화면명 | 필수첨부파일관리 |
| 화면 유형 | [v] 조회 [v] 입력 [v] 수정 [v] 삭제 [ ] 출력 |
| 메뉴 경로 | 디지털행정지원 > 사이트관리 > 필수첨부파일관리 |
---
## 2. 화면 목적
문서(업무) 구분별로 **필수 첨부파일** 항목을 등록·조회·수정·삭제한다. 상단 검색 영역에서 **문서구분**으로 목록을 필터링하고, **등록** 또는 행별 **수정**·**삭제**로 마스터 데이터를 유지한다. 등록·수정 시 **필수첨부파일아이디**는 클라이언트에서 입력(또는 생성)하여 요청 본문에 포함한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 페이지 타이틀: 필수첨부파일관리 브레드크럼: 디지털행정지원 > … > │
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ [영역 A] 검색 / 액션 │
│ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 문서구분 [전체 ▼] [조회] [등록] │ │
│ └────────────────────────────────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ [영역 B] 필수첨부파일 목록 │
│ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ 필수첨부파일아이디 | 문서구분 | 필수첨부파일명 | 필수첨부여부 | 비고 | 등록일시 | 수정일시 | 관리 │
│ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ EAF-0001 | 설치인가 | 사업계획서 | 예 | 서명본 첨부 | 2026-04-22 10:10:00 | — | [수정][삭제] │ │
│ │ EAF-0002 | 기본계획 | 변경사유서 | 예 | — | 2026-04-22 11:00:00 | — | [수정][삭제] │ │
│ └────────────────────────────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
(모달) [다이얼로그 C] 필수첨부파일 등록 / 수정
┌──────────────────────────────────────────────────────┐
│ 필수첨부파일 등록 [닫기] │
├──────────────────────────────────────────────────────┤
│ 필수첨부파일아이디(자동발급) [EAF-0004] │
│ 문서구분 [설치인가 ▼] │
│ 필수첨부파일명 [예: 사업계획서] │
│ 필수첨부여부 [ ] 예 [ ] 아니오 │
│ 비고 [비고를 입력하세요] │
├──────────────────────────────────────────────────────┤
│ [취소] [저장] │
└──────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
CALL 필수첨부파일 목록 조회 (findPage)
DISPLAY 필수첨부파일 그리드 (영역 B)
// 영역 A: 검색
ON [조회] 버튼 CLICK DO
CALL 필수첨부파일 목록 조회 (문서구분 = 선택값, findPage)
DISPLAY 필수첨부파일 그리드 (영역 B)
END ON
// 영역 A: 등록
ON [등록] 버튼 CLICK DO
OPEN 필수첨부파일 등록 다이얼로그 (다이얼로그 C)
ON [저장] DO
CONFIRM "등록하시겠습니까?"
VALIDATE 문서구분, 필수첨부파일명, 필수첨부여부
CALL 필수첨부파일 등록 (create)
CALL 필수첨부파일 목록 조회
CLOSE 다이얼로그
TOAST "저장되었습니다."
END ON
ON [취소] / [닫기] DO
CLOSE 다이얼로그
END ON
END ON
// 영역 B: 수정
ON 행 [수정] CLICK DO
OPEN 필수첨부파일 수정 다이얼로그 (기존 값 초기화)
ON [저장] DO
VALIDATE 문서구분, 필수첨부파일명, 필수첨부여부
CALL 필수첨부파일 수정 (update)
CALL 필수첨부파일 목록 조회
CLOSE 다이얼로그
END ON
END ON
// 영역 B: 삭제
ON 행 [삭제] CLICK DO
CONFIRM "삭제하시겠습니까?"
CALL 필수첨부파일 삭제 (remove)
CALL 필수첨부파일 목록 조회
END ON
END
```
---
## 4. 영역 A: 검색 / 액션
문서구분 조건으로 목록을 조회하고, 신규 등록 다이얼로그를 연다.
### 4-1. 검색 필드
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| -------- | ---------- | ---- | ------ | ----------------- | ------------------------------ |
| 문서구분 | CodeSelect | N | 전체 | `docSeComCd?` | "전체" 선택 시 파라미터 미전송 |
### 4-2. 버튼
| 버튼명 | 위치 | 동작 |
| ------ | -------------- | ----------------------------------------------- |
| 조회 | 검색 필드 우측 | 현재 검색 조건으로 영역 B 목록 갱신 |
| 등록 | 영역 우측 끝 | 필수첨부파일 등록 다이얼로그(다이얼로그 C) 오픈 |
---
## 5. 영역 B: 필수첨부파일 목록
필수 첨부파일 마스터를 테이블로 표시한다. 페이지네이션 UI 적용.
### 5-1. 헤더
| 항목 | 내용 |
| ----------- | ----------------- |
| 섹션 타이틀 | 필수첨부파일 목록 |
### 5-2. 그리드 컬럼
| 컬럼명 | Field | 타입 | 비고 |
| ------------------ | ------------------- | ------ | ------------------------------------- |
| 필수첨부파일아이디 | `esntlAtchFileId` | string | 예: `EAF-0001`, 클라이언트 생성/입력 |
| 문서구분 | `docSeComCd` | string | 코드명 표시 — 공통코드 (TBD 상위코드) |
| 필수첨부파일명 | `esntlAtchFileNm` | string | |
| 필수첨부여부 | `esntlAtchYn` | string | `Y`→예, `N`→아니오 |
| 비고 | `esntlAtchFileRmrk` | string | 없으면 `—` 표시 |
| 등록일시 | `regDt` | string | `yyyy-MM-dd HH:mm:ss` |
| 수정일시 | `mdfcnDt` | string | 미수정 시 빈 값 또는 `—` |
| 관리 | — | — | 행별 **[수정] · [삭제]** |
- 행 클릭 시 별도 상세 화면 없음. 편집은 **[수정]** 버튼으로 다이얼로그 C를 연다.
### 5-3. 버튼
| 버튼명 | 위치 | 동작 |
| ------ | ---- | -------------------------------------- |
| 수정 | 행별 | 필수첨부파일 수정 다이얼로그 (기존 값) |
| 삭제 | 행별 | 확인 후 삭제 API 호출 → 목록 갱신 |
---
## 6. 다이얼로그 C: 필수첨부파일 등록 / 수정
등록·수정 공용 폼 다이얼로그. 등록 시 제목 **「필수첨부파일 등록」**, 수정 시 **「필수첨부파일 수정」**.
### 6-1. 폼 필드
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| ------------------ | ---------- | ---- | ------ | ------------------- | -------------------------------- |
| 필수첨부파일아이디 | (Readonly) | Y | — | `esntlAtchFileId` | 등록 시 미노출 |
| 문서구분 | CodeSelect | Y | — | `docSeComCd` | 예: 설치인가, 기본계획 |
| 필수첨부파일명 | Text | Y | — | `esntlAtchFileNm` | placeholder: "예: 사업계획서" |
| 필수첨부여부 | RadioGroup | Y | Y | `esntlAtchYn` | Y/N, UI 표시: 예/아니오 |
| 비고 | Textarea | N | — | `esntlAtchFileRmrk` | placeholder: "비고를 입력하세요" |
### 6-2. 버튼
| 버튼명 | 동작 |
| ------ | -------------------------------------- |
| 저장 | POST(등록) 또는 PUT(수정) 후 목록 갱신 |
| 취소 | 다이얼로그 닫기, 입력값 폐기 |
| 닫기 | 다이얼로그 닫기 (헤더 우측) |
---
## 7. API/DTO 명세
### 7-1. 공통
| 항목 | 내용 |
| ----------- | ----------------------------------------------------- |
| 서비스 | AS Common Service |
| Base prefix | `/as-service/api/v1/esntl-atch-file` |
| 인증 | JWT (`Authorization`), `X-Site-Id` 등 |
| 페이징 | Spring `Pageable` 사용 (`page`, `size`, `sort`) |
| 엔티티 | `@/entities/esntl` (`esntl-service.ts`, `queries.ts`) |
### 7-2. 필수첨부파일 API
| 기능 | Method | URL | Request DTO | Response DTO | API (클라이언트 함수명) |
| --------- | ------ | -------------------- | ----------------------------- | ----------------------------------- | ----------------------- |
| 트리 조회 | GET | `/list/tree` | Query: `docSeComCd`(필수) | `EsntlAtchFileMngTreeResponseDto[]` | `findTree` |
| 목록 조회 | GET | `/` | Query: Search + Pageable | `PageResponse<EsntlAtchFileMng>` | `findPage` |
| 단건 조회 | GET | `/{esntlAtchFileId}` | Path: `esntlAtchFileId` | `EsntlAtchFileMng` | `findById` |
| 등록 | POST | `/` | `EsntlAtchFileMngSaveRequest` | `string` (생성된 ID) | `create` |
| 수정 | PUT | `/{esntlAtchFileId}` | `EsntlAtchFileMngSaveRequest` | `string` (수정된 ID) | `update` |
| 삭제 | DELETE | `/{esntlAtchFileId}` | Path: `esntlAtchFileId` | `string` (삭제된 ID) | `remove` |
**응답 DTO**
| Field | 타입 | 설명 |
| ------------------- | ------- | -------------------------------- |
| `esntlAtchFileId` | string | 필수첨부파일아이디 |
| `docSeComCd` | string | 문서구분공통코드 |
| `esntlAtchFileNm` | string | 필수첨부파일명 |
| `esntlAtchYn` | string | 필수첨부여부 (Y/N) |
| `esntlAtchFileRmrk` | string? | 필수첨부파일비고 |
| `rgtr` | string? | 등록자 UUID |
| `rgtrNm` | string? | 등록자명 |
| `mdfr` | string? | 수정자 UUID |
| `regDt` | string? | 등록일시 (`yyyy-MM-dd HH:mm:ss`) |
| `mdfcnDt` | string? | 수정일시 |
**저장 Request**
| Field | 필수 | 설명 |
| ------------------- | ---- | ------------------ |
| `esntlAtchFileId` | Y | 필수첨부파일아이디 |
| `docSeComCd` | N | 문서구분 |
| `esntlAtchFileNm` | N | 필수첨부파일명 |
| `esntlAtchYn` | N | 필수첨부여부 |
| `esntlAtchFileRmrk` | N | 비고 |
**검색 Query**
| Field | 필수 | 설명 |
| ----------------- | ---- | ----------------------------- |
| `docSeComCd` | N | 문서구분 (전체 시 미전송) |
| `esntlAtchFileNm` | N | 필수첨부파일명 부분/일치 검색 |
| `esntlAtchYn` | N | 필수첨부여부 (`Y`/`N`) |
| `page` | N | 페이지 번호 (0-base) |
| `size` | N | 페이지 크기 (기본값: 100) |
| `sort` | N | 정렬 조건 (예: `regDt,desc`) |
### 7-3. 공통코드
| UI 필드 | 코드 그룹 | 예시 값 |
| ------------ | --------- | ------------------ |
| 문서구분 | ASDOCSECD | 설치인가, 기본계획 |
| 필수첨부여부 | 고정 | Y(예), N(아니오) |
---
## 8. UI 구현 가이드
### 8-1. 기술 스택·원칙
- **공통 컴포넌트**: `@common/components` (`Dialog`, `Button`, `Input`, `Select`, `Textarea`, `Label` 등)
- **테이블**: `@tanstack/react-table` + `useDataTable`
- **폼**: `react-hook-form`
- **데이터**: React Query — `@/entities/esntl/api/queries.ts` 훅 사용
- **공통코드**: `@/entities/code` — `CodeSelect`, `findCodeLabel`
- **확인**: `@frontend-opensource/use-react-hooks` — `useConfirm` (등록·삭제 확인)
### 8-2. 모듈·파일 배치
| 구분 | 경로 |
| ------ | ---------------------------------------------------------------------------------------------------- |
| 라우트 | `apps/as/app/(with-base-layout)/esntl-atch-file-mng/page.tsx` |
| 페이지 | `apps/as/src/pages/esntl-atch-file-mng/` (Public API: page만 export) |
| UI | `.../ui/esntl-atch-file-mng-page.tsx`, `esntl-atch-file-list.tsx`, `esntl-atch-file-form-dialog.tsx` |
| model | `.../model/esntl-atch-file-columns.tsx`, `esntl-atch-file-search-params.ts` |
| 엔티티 | `apps/as/src/entities/esntl/` (`esntl-service.ts`, `queries.ts`, `esntl-interface.ts`) |
| 문서 | `apps/as/docs/essential-attachment-file-management/SPEC.md` |
### 8-3. 데이터 흐름(요약)
- **진입**: 검색 params 기본값(`docSeComCd` = 전체) → 목록 query → 영역 B DataTable.
- **조회**: [조회] 클릭 시 search state 반영 → 목록 query refetch.
- **등록**: [등록] → Dialog `mode=create` → [저장] → POST → 목록 invalidate.
- **수정**: [수정] → Dialog `mode=edit` + row 초기값 → [저장] → PUT → 목록 invalidate.
- **삭제**: confirm → DELETE → 목록 invalidate.
+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
```
+352
View File
@@ -0,0 +1,352 @@
# 화면설계서: 알림이력
## 1. 기본 정보
| 항목 | 내용 |
| ----------- | -------------------------------------------- |
| 버전 | 1.4 |
| 프로세스명 | UI설계 |
| 화면명 | 알림이력 |
| 화면 ID | AS_SC0205011 |
| 요구사항 ID | NSUR-001-001 |
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
| 메뉴 경로 | 메인 > 행정지원서비스 > 알림이력 |
---
## 2. 화면 목적
로그인한 사용자가 **본인에게 발송된 행정지원(ADSS) 알림**을 기간·구분으로 조회한다. 목록의 처리 버튼으로 해당 알림을 읽음 처리한 뒤, 연계된 업무 화면으로 이동한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 브레드크럼: 메인 > 행정지원서비스 > 알림이력 │
│ 페이지 타이틀: 알림이력 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 A] 검색 필터 │
│ 기간 [시작일] ~ [종료일] 구분 ▼ [검색] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 B] 알림 이력 목록 │
│ 타이틀: 알림 이력 목록 N개 ▼ │
│ ┌────┬──────────────┬────────────────┬──────────┬────────┬────────┐ │
│ │ No │ 알림일시 │ 알림내용 │ 연계업무 │ 상태 │ 처리 │ │
│ ├────┼──────────────┼────────────────┼──────────┼────────┼────────┤ │
│ │ … │ regDt │ notiTtl │ docSeComNm│ Badge │ 버튼 │ │
│ └────┴──────────────┴────────────────┴──────────┴────────┴────────┘ │
│ [페이지네이션] │
└─────────────────────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
// 기간 기본값: 오늘 기준 최근 1개월 (시작일 = today-1M, 종료일 = today)
CALL findMyAsNotiList(NotiSearchDto + pageable)
DISPLAY 알림 이력 목록 그리드 (영역 B)
ON [검색] 버튼 CLICK DO
CALL findMyAsNotiList(영역 A 조건 + page=0)
DISPLAY 알림 이력 목록 그리드 (영역 B)
END ON
ON 페이지네이션 CHANGE DO
CALL findMyAsNotiList(영역 A 조건 + page/size)
DISPLAY 알림 이력 목록 그리드 (영역 B)
END ON
ON 영역 B [처리] 버튼 CLICK DO
CALL readNoti(notiId)
// UI설계: 해당 알림에 연계된 URL로 이동
NAVIGATE notiUrl
END ON
END
```
---
## 4. 영역 A — 검색 필터
구성: **기간 · 구분** (+ 검색).
서버 `findMyAsNotiList` 고정 정책 (클라이언트 미전달):
- `recvUnqId` = 로그인 사용자 UUID
- `delYn` = `N`
- `notiType` = `ADSS` (행정지원 전용 API)
### 4-1. 필드 정의
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| ---------- | ---------- | ---- | --------------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
| 기간(시작) | DatePicker | N | 오늘 − 1개월 (`YYYY-MM-DD`) | `startDt` | `NotiSearchDto.startDt` |
| 기간(종료) | DatePicker | N | 오늘 (`YYYY-MM-DD`) | `endDt` | `NotiSearchDto.endDt` |
| 구분 | Select | N | 전체(미선택) | `docSeComCd` | `INSTL` / `FNRS` / `SEWER` / `CNFM` / `PWRK` (또는 단축 `I`/`F`/`S`/`C`/`P`). 공통코드 `ASDOCSE` |
### 4-2. 버튼
| 버튼명 | 동작 |
| ------ | -------------------------------------------------------------- |
| 검색 | 영역 A 조건으로 `findMyAsNotiList` 호출 → 영역 B 갱신 (page=0) |
### 4-3. 콤보·기본값 주의
| 항목 | 내용 |
| -------------- | ---------------------------------------------------------- |
| 구분 = 전체 | `docSeComCd` 미전달. `notiType`은 서버가 `ADSS`로 고정 |
| 구분 옵션 소스 | 공통코드 `ASDOCSE` (`docSeComCd` ↔ `docSeComNm`) |
| 기간 | UI설계 상세설명: 기본 최근 1개월. `startDt`/`endDt`로 전달 |
---
## 5. 영역 B — 알림 이력 목록
### 5-1. 그리드 컬럼
| 표시명 | Field | 비고 |
| -------- | ------------ | --------------------------------------------------------------- |
| No | (row index) | 페이지 오프셋 반영 |
| 알림일시 | `regDt` | 표시 `YYYY-MM-DD HH:mm` (응답 `yyyy-MM-dd HH:mm:ss`) |
| 알림내용 | `notiTtl` | 제목 문자열. UI 샘플은 `[YYYY-MM-DD] …` 접두 |
| 연계업무 | `docSeComNm` | 문서구분 공통코드명 (`docSeComCd` + `@CodeName` `ASDOCSE`) |
| 상태 | (derived) | `Badge` 렌더링. 판정 규칙은 §5-1-1 |
| 처리 | (action) | 상태별 버튼 라벨. 클릭 시 `readNoti` 후 `notiUrl`로 이동 (§5-3) |
#### 5-1-1. 상태 UI 케이스 (`@common/components` `Badge`)
판정에 사용: `notiPrcsCmptnYn`, `notReadYn`, `regDt` (오늘 기준 경과일).
| 조건 | 라벨 | Badge |
| ------------------------------------------------------------------------------ | -------- | ----------------------------- |
| `notiPrcsCmptnYn` = `Y` | 처리완료 | `variant="default"` (primary) |
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `Y` 이고 `regDt`로부터 **7일 초과** | 처리지연 | `color="red"` |
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `Y` 이고 `regDt`로부터 **7일 이내** | 처리중 | `color="green"` |
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `N` 이고 `regDt`로부터 **3일 초과** | 지연 | `color="orange"` |
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `N` 이고 `regDt`로부터 **3일 이내** | 미조회 | `color="yellow"` |
```
IF notiPrcsCmptnYn = Y THEN
DISPLAY Badge "처리완료" (primary / variant=default)
ELSE IF notReadYn = Y THEN
IF daysSince(regDt) > 7 THEN
DISPLAY Badge "처리지연" (red)
ELSE
DISPLAY Badge "처리중" (green)
END IF
ELSE // notReadYn = N
IF daysSince(regDt) > 3 THEN
DISPLAY Badge "지연" (orange)
ELSE
DISPLAY Badge "미조회" (yellow)
END IF
END IF
```
- `daysSince(regDt)`: 오늘 날짜 − `regDt` 일자 (일 단위)
- `regDt`가 없으면 상태 Badge 표시 전략 TBD (미표시 또는 기본값)
응답에 있으나 그리드에 **표시하지 않음**:
| Field | 비고 |
| ----------------- | ---------------------------------------------------------------------- |
| `notiId` | 알림 ID — 행 키 |
| `recvUnqId` | 수신 회원 UUID (서버가 로그인 사용자로 고정) |
| `senderUnqId` | 발신 회원 UUID |
| `notiType` | 알림 종류 (`ADSS`) |
| `notiCn` | 알림 내용 (목록에서는 서버가 제외할 수 있음) |
| `notReadYn` | 확인 여부 `Y`/`N`. 상태 Badge 판정에 사용 (§5-1-1) |
| `notiUrl` | 처리 버튼 이동 URL. 컬럼 미표시 |
| `notiLnkgUnqNo` | 알림연결고유번호 |
| `docSeComCd` | 문서구분 공통코드. 연계업무 표시는 `docSeComNm` 사용 |
| `notiIdntyDt` | 알림 확인 일시 |
| `notiIdntyYn` | 알림 확인 여부 (`notReadYn`과 동일 의미). 상태 판정은 `notReadYn` 사용 |
| `notiPrcsCmptnYn` | 알림 처리 완료 여부. 상태 Badge 판정에 사용 (§5-1-1) |
| `notiPrcsCmptnDt` | 알림 처리 완료 일시 |
### 5-2. 목록 메타·페이지네이션
| 항목 | 매핑 |
| ------------- | ------------------------------------------- |
| 건수 / size ▼ | 상단 우측 `N개` 셀렉트 → `Pageable.size` |
| page / size | `NotiSearchDto`의 `PageableParams` |
| 페이지 컨트롤 | Spring `Page` 표준 (`number`, `totalPages`) |
### 5-3. 행 상호작용
| 동작 | 설명 |
| -------------- | --------------------------------------------------------------------------- |
| 처리 버튼 클릭 | `readNoti(notiId)`로 읽음 처리 후, 해당 알림에 연계된 URL(`notiUrl`)로 이동 |
| 행 클릭 | UI설계에 없음. 처리 버튼만 명시 |
**처리 버튼 라벨** (상태 Badge §5-1-1 기준):
| 상태 | 버튼명 |
| -------- | -------- |
| 미조회 | 처리하기 |
| 지연 | 처리하기 |
| 처리중 | 처리하기 |
| 처리지연 | 처리하기 |
| 처리완료 | 상세보기 |
- `notiUrl`이 비어 있으면 이동하지 않음 (no-op). 읽음 처리는 `notiUrl` 유무와 관계없이 호출
- 읽음 처리 성공 후 목록 쿼리(`notiBaseKey`)를 invalidate 하여 영역 B 상태를 갱신
---
## 6. API/DTO 명세
### 6-1. 공통
| 항목 | 값 |
| --------------- | ---------------------------------------------------------------------------------------- |
| Base prefix | `/cms-service/api/v1/noti` (`ENDPOINT.CMS_SERVICE.NOTI`) |
| 인증 | 로그인 사용자 UUID — 서버 `SecurityUtils.getLoggedInUserUnqIdUUID()` (클라이언트 미전달) |
| 클라이언트 모듈 | `@/entities/noti` — `notiService` / `useFindMyAsNotiList` / `useReadNoti` |
### 6-2. 로그인한 사용자 행정지원 알림 목록 — `findMyAsNotiList`
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ---------------------------------- | ------ | ------------------- | --------------------------------- | ------------------------------------- | ------------------------------------------------------ |
| 로그인한 사용자 행정지원 알림 목록 | GET | `{BASE}/as/my/list` | Query: `NotiSearchDto` + Pageable | `PageResponse<NotiAsUserResponseDto>` | `notiService.findMyAsNotiList` / `useFindMyAsNotiList` |
**Request (`NotiSearchDto` + Pageable)**
| 필드 | 필수 | 설명 |
| ------------------------ | ---- | --------------------------------------------------------------------------- |
| `recvUnqId` | N | 수신 회원 UUID. 서버가 로그인 사용자로 덮어씀 |
| `senderUnqId` | N | 발신 회원 UUID. 본 화면 필터에 없음 |
| `notiType` | N | 알림 종류. 본 API는 서버가 `ADSS`로 고정 |
| `notiTtlKeyword` | N | 알림 제목 키워드. 본 화면 필터에 없음 |
| `notiCnKeyword` | N | 알림 내용 키워드. 본 화면 필터에 없음 |
| `notReadYn` | N | 알림 확인 여부 (`Y`/`N`). 본 화면 검색 필터에 없음 |
| `delYn` | N | 삭제 여부. 서버가 `N`으로 고정 |
| `startDt` | N | 시작일자 `YYYY-MM-DD` — 영역 A 기간(시작) |
| `endDt` | N | 종료일자 `YYYY-MM-DD` — 영역 A 기간(종료) |
| `docSeComCd` | N | 문서 구분 공통 코드 (`INSTL`/`FNRS`/`SEWER`/`CNFM`/`PWRK` 등) — 영역 A 구분 |
| `page` / `size` / `sort` | N | Spring Pageable |
**Response (`NotiAsUserResponseDto`)**
| 필드 | 타입 | 설명 |
| ----------------- | ------- | ------------------------------------------------------- |
| `notiId` | number | 알림 ID |
| `recvUnqId` | string | 수신 회원 UUID |
| `senderUnqId` | string | 발신 회원 UUID |
| `notiType` | string | 알림 종류 (`ADSS`) |
| `notiTtl` | string | 알림 제목 |
| `notiCn` | string? | 알림 내용 (목록에서 제외될 수 있음) |
| `notReadYn` | string? | 알림 확인 여부 (`Y`/`N`). 상태 Badge 판정 |
| `regDt` | string? | 등록 일시 `yyyy-MM-dd HH:mm:ss`. 상태 Badge 경과일 기준 |
| `notiUrl` | string? | 알림 URL 주소 |
| `notiLnkgUnqNo` | string? | 알림연결고유번호 |
| `docSeComCd` | string? | 문서구분 공통코드 |
| `docSeComNm` | string? | 문서구분 공통코드명 (`ASDOCSE`) |
| `notiIdntyDt` | string? | 알림 확인 일시 `yyyy-MM-dd HH:mm:ss` |
| `notiIdntyYn` | string? | 알림 확인 여부 (`Y`/`N`) |
| `notiPrcsCmptnYn` | string? | 알림 처리 완료 여부 (`Y`/`N`). 상태 Badge 판정 |
| `notiPrcsCmptnDt` | string? | 알림 처리 완료 일시 `yyyy-MM-dd HH:mm:ss` |
### 6-3. 알림 읽음 처리 — `readNoti`
| 기능 | Method | URL | Request DTO | Response DTO | API |
| -------------- | ------ | ---------------------- | ----------------------- | ---------------- | -------------------------------------- |
| 알림 읽음 처리 | PUT | `{BASE}/{notiId}/read` | Path: `notiId` (`Long`) | `Long` (알림 ID) | `notiService.readNoti` / `useReadNoti` |
**Request**
| 필드 | 위치 | 필수 | 설명 |
| -------- | ---- | ---- | ------- |
| `notiId` | Path | Y | 알림 ID |
Body 없음.
**Response**
| 타입 | 설명 |
| ------ | ------------------------------ |
| number | 읽음 처리된 알림 ID (`notiId`) |
화면 매핑: 영역 B [처리] 버튼 클릭 시 호출. 성공 시 `notiBaseKey` invalidate → `findMyAsNotiList` 재조회.
---
## 7. UI 구현 가이드
### 7-1. 기술 스택·원칙
- UI: `@common/components` — DatePicker, Select, Button, DataTable, Pagination, **Badge**
- 목록: `@tanstack/react-table` + `useDataTable` (기존 `my-pbadms-sprt-list` 패턴)
- 데이터: React Query `useFindMyAsNotiList(params)`, `useReadNoti()`
- 검색 조건·페이지: URL `searchParams` 동기화 권장 (`useSearchParamsState` 스킬)
- 처리 컬럼: 행 클릭이 아니라 **버튼 클릭**으로 `readNoti` 후 `notiUrl` 이동
- 상태 컬럼: §5-1-1 규칙으로 `Badge` 렌더링 (`notiPrcsCmptnYn` / `notReadYn` / `regDt`)
- 연계업무: `docSeComNm` 표시
### 7-2. 앱·파일 배치
| 구분 | 경로 |
| ------------ | --------------------------------------------------------------------- |
| 라우트 | `app/(main)/(with-base-layout)/noti/page.tsx` |
| 페이지 | `pages/noti/ui/noti-list-page.tsx` |
| 목록 그리드 | `pages/noti/ui/noti-list.tsx` |
| 테이블 컬럼 | `pages/noti/model/noti-table-columns.tsx` |
| 상태 판정 | `pages/noti/lib/resolve-noti-status.ts` |
| Entity API | `entities/noti/api/noti-service.ts` |
| Entity Hook | `entities/noti/api/queries.ts` → `useFindMyAsNotiList`, `useReadNoti` |
| Entity Model | `entities/noti/model/noti-interface.ts` |
화면 레이어(`pages/noti`)·Entity(`findMyAsNotiList`, `readNoti`) 구현 완료.
### 7-3. 영역별 UI 매핑
| 영역 | 컴포넌트 | 데이터 |
| ---- | ------------------- | -------------------------------------------------------------- |
| A | `noti-list-page` | 로컬 state → `NotiSearchDto` |
| B | DataTable + columns | `useFindMyAsNotiList` → `content[]`. 처리 버튼 → `useReadNoti` |
### 7-4. 다이얼로그·라우팅
| 항목 | 내용 |
| ----------- | --------------------------------------------------------------------- |
| 다이얼로그 | 없음 (목록 전용) |
| 목록 라우트 | `/noti` |
| 상세 이동 | 처리 버튼 → `readNoti(notiId)` 후 `notiUrl` (절대/상대 경로 형식 TBD) |
### 7-5. 데이터 흐름
```
BEGIN
// 최초 진입
SET 기간 = 최근 1개월 // startDt / endDt 전달
CALL findMyAsNotiList
DISPLAY 영역 B
ON [검색] CLICK DO
CALL findMyAsNotiList // → 영역 B 갱신
END ON
ON 페이지 CHANGE DO
CALL findMyAsNotiList
END ON
ON 영역 B [처리] 버튼 CLICK DO
CALL readNoti(notiId) // → 영역 B 상태 갱신 (invalidate)
IF NOT notiUrl THEN
// no-op (읽음 처리는 수행됨)
ELSE
NAVIGATE notiUrl
END IF
END ON
END
```
@@ -0,0 +1,441 @@
# 화면설계서: 이전기본계획조회 (기본 계획 찾기)
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | --------------------------------------------------------------- |
| 버전 | 0.2 |
| 프로세스명 | UI설계 |
| 화면명 | 이전기본계획조회 (기본 계획 찾기) |
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
| 메뉴 경로 | 하수도기본계획 등록·수정 > 사업개요 > [이전기본계획조회] Dialog |
---
## 2. 화면 목적
하수도기본계획 등록·수정 화면에서 **이전(기수립) 기본계획** 수치와 관련 보고서를 **조회·다운로드**한다. 시도·구군·연도·처리시설을 선택한 뒤 총설·처리구역별 현황을 표시하고, 분류 트리에서 기본계획 파일을 내려받는다. **부모 사업개요 폼에는 수치를 반영하지 않는다**(레거시 `bscPlnPopup.jsp`와 동일).
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
부모: 하수도기본계획 등록/수정 폼 → 사업개요 섹션의 `[이전기본계획조회]` 버튼으로 Dialog 오픈.
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Dialog 타이틀: 기본 계획 찾기 [닫기] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 A] 검색 필터 │
│ 시도 ▼ 구군 ▼ 연도 ▼ 처리시설 ▼ [조회] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 B] 기본계획파일 │
│ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
│ │ [영역 B-1] 분류 트리 │ [영역 B-2] 파일 목록 │ │
│ │ (약 40%) │ (약 60%) │ │
│ │ └ 노드… │ 파일명 / 다운로드 │ │
│ └──────────────────────┴────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 C] 총설 │
│ ┌────────┬──────────────당초──────────────┬──기준──┬──────────────변경──────────────┐ │
│ │ 구분 │ 1단계 │ 2단계 │ 3단계 │ 4단계 │ 기준 │ 1단계 │ 2단계 │ 3단계 │ 4단계 │ │
│ ├────────┼───────┼───────┼───────┼───────┼────────┼───────┼───────┼───────┼───────┤ │
│ │계획목표│ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ │
│ │년도 │ │ │ │ │ │ │ │ │ │ │
│ └────────┴───────┴───────┴───────┴───────┴────────┴───────┴───────┴───────┴───────┘ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [영역 D] 처리구역별 현황 │
│ ┌────────┬──────────────당초──────────────┬──기준──┬──────────────변경──────────────┐ │
│ │ 구분 │ 1~4단계(+연도헤더) │ 기준 │ 1~4단계(+연도헤더) │ │
│ ├────────┼────────────────────────────────┼────────┼────────────────────────────────┤ │
│ │ 하수처리인구 … 배제방식 (읽기 전용 행) │ │
│ └────────┴────────────────────────────────┴────────┴────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 부모 폼 — 사업개요
ON [이전기본계획조회] 버튼 CLICK DO
OPEN PreviousPlanLookupDialog
// 프리필: 부모 폼 sewerBscPlan.sido / gugun / admdstCd
CALL findSidoCombo()
DISPLAY 시도 옵션 (영역 A)
IF 시도 프리필 THEN
CALL findGugunCombo(sidoCd)
DISPLAY 구군 옵션 (영역 A)
CALL getLvNowDmgMtncGnsvy({ level: "1", ctpvNm })
// TBD: useLvNowDmgMtncGnsvy enabled — level별 필수 파라미터만 검사하도록 조정
IF 구군 프리필 THEN
CALL getLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })
DISPLAY 연도 옵션 (영역 A) // children 중 level=3 노드의 gnsvyYr
CALL getClsfSystm({ clsfSystmNm: gugunNm })
DISPLAY 분류 트리 (영역 B-1)
END IF
END IF
END ON
// 영역 A: 시도 변경
ON 영역 A 시도 CHANGE DO
RESET 구군 / 연도 / 처리시설
CALL findGugunCombo(sidoCd)
CALL getLvNowDmgMtncGnsvy({ level: "1", ctpvNm })
// 구군 미선택 → 연도·처리시설 옵션 비움, 트리 빈 안내
END ON
// 영역 A: 구군 변경
ON 영역 A 구군 CHANGE DO
RESET 연도 / 처리시설
CALL getLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })
DISPLAY 연도 옵션 (영역 A) // level=3 노드 → gnsvyYr
CALL getClsfSystm({ clsfSystmNm: sggNm })
DISPLAY 분류 트리 (영역 B-1)
END ON
// 영역 A: 연도 변경
ON 영역 A 연도 CHANGE DO
RESET 처리시설
CALL getLvNowDmgMtncGnsvy({ level: "3", ctpvNm, sggNm, gnsvyYr })
DISPLAY 처리시설 옵션 (영역 A) // level=3 노드 children → swtrmtFcltCd / swtrmtFcltNm
END ON
// 영역 A: 조회
ON [조회] 버튼 CLICK DO
VALIDATE 시도, 구군, 연도(targetYear), 처리시설(swtrmtFcltCd)
CALL getNowDmgMtncGnsvy({ swtrmtFcltCd, ctpvNm, sggNm, targetYear })
DISPLAY 총설 (영역 C) // 응답[0]의 planYr / planYrDt* / planYrDc*
DISPLAY 처리구역별 현황 (영역 D) // 응답[0]의 지표 Dt1~4 / base / Dc1~4
END ON
// 영역 B: 트리 노드 선택
ON 영역 B-1 노드 CLICK DO
CALL getFile({ clsfSystmSn, sort, vetPrcsYn }) // Response DTO TBD
DISPLAY 파일 목록 (영역 B-2)
END ON
// 영역 B: 파일 다운로드
ON 영역 B-2 파일 CLICK DO
CALL getFilePrcs(fileSn) // 파일 메타(경로·제목) — Response TBD
CALL fileDownload({ filePath, fileName })
// 브라우저 다운로드 트리거
END ON
ON [닫기] DO
CLOSE PreviousPlanLookupDialog
// 부모 폼 값 변경 없음
END ON
END
```
---
## 4. 영역 A — 검색 필터
구성: **시도 · 구군 · 연도 · 처리시설** (+ 조회).
- 시도·구군: `adstdstr` 콤보 (기존 스펙 유지)
- 연도·처리시설: 각각 `useLvNowDmgMtncGnsvy`를 **별도 호출**. `level`에 따라 반환 계층이 달라진다.
### 4-1. 필드 정의
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| -------- | --------- | ---- | ------------------------- | ------------------------------------------------ | -------------------------------------------------------------- |
| 시도 | Select | Y | 부모 `sewerBscPlan.sido` | `ctpvNm`(표시명), 코드는 `adstDstrCd` | `useFindSidoCombo` |
| 구군 | Select | Y | 부모 `sewerBscPlan.gugun` | `sggNm`(표시명), 코드는 `adstDstrCd` | `useFindGugunCombo(sidoCd)` |
| 연도 | Select | Y | 없음 | `gnsvyYr` / 조회 시 `targetYear` | `useLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })` |
| 처리시설 | Select | Y | 없음 | `swtrmtFcltCd` ← 옵션 value, 라벨 `swtrmtFcltNm` | `useLvNowDmgMtncGnsvy({ level: "3", ctpvNm, sggNm, gnsvyYr })` |
> 처리시설은 `bns-if` `useFacilityList`를 **사용하지 않는다**.
### 4-2. 버튼
| 버튼명 | 동작 |
| ------ | -------------------------------------------------------- |
| 조회 | 필수값 검증 후 `getNowDmgMtncGnsvy` 호출 → 영역 C·D 갱신 |
| 닫기 | Dialog 종료 (부모 미반영) |
### 4-3. `lvNowDmgMtncGnsvy` level 캐스케이드
| 트리거 | Request `level` | Request 파라미터 | UI에 쓰는 응답 | 옵션 추출 |
| --------- | --------------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------ |
| 시도 선택 | `"1"` | `ctpvNm` | (하위 초기화용) | 연도·처리시설 Select 리셋. 구군은 `findGugunCombo` |
| 구군 선택 | `"2"` | `ctpvNm`, `sggNm` | 연도 Select | 트리에서 `level === 3` 노드의 `gnsvyYr` |
| 연도 선택 | `"3"` | `ctpvNm`, `sggNm`, `gnsvyYr` | 처리시설 Select | 선택 연도(`level === 3`) 노드의 `children[]` → `swtrmtFcltCd` / `swtrmtFcltNm` |
상위 변경 시 하위 선택값·옵션을 RESET한다 (시도→구군·연도·시설 / 구군→연도·시설 / 연도→시설).
---
## 5. 영역 B — 기본계획파일
### 5-1. 트리 (B-1)
| 표시명 | Field | 비고 |
| ------ | --------------- | ------------------------ |
| 분류명 | `clsfSystmNm` | `ClsfSystmResponseDto` |
| 노드키 | `clsfSystmSn` | 선택 시 파일 목록 조회 |
| 상위키 | `upClsfSystmSn` | 클라이언트에서 트리 조립 |
| 정렬 | `sortSeq` | |
| 경로 | `path` | |
- 조회 파라미터: `clsfSystmNm` = 선택 구군명(레거시와 동일). `upClsfSystmSn`은 하위 전개 시 TBD.
- 시도·구군 미선택 시: 「시도와 구군을 선택하세요.」 안내.
### 5-2. 파일 목록 (B-2)
| 표시명 | Field | 비고 |
| ------ | ----- | ------------------------------------------------------------------- |
| 파일명 | TBD | `getFile` Response DTO가 `unknown` — 백엔드 스키마 확정 후 매핑 |
| (기타) | TBD | 레거시: 트리 선택 후 목록 표시, 클릭 시 `fileprcs` → `fileDownload` |
### 5-3. 버튼·상호작용
| 동작 | 설명 |
| -------------- | -------------------------------------------------------------- |
| 트리 노드 클릭 | `getFile({ clsfSystmSn, … })` → B-2 목록 |
| 파일 클릭 | `getFilePrcs(fileSn)` → `fileDownload({ filePath, fileName })` |
| 빈 목록 | 「트리에서 항목을 선택하세요.」 / 「파일이 없습니다.」 |
---
## 6. 영역 C — 총설
### 6-1. 테이블 구조
| 행(구분) | 열 매핑 (당초1~~4 / 기준 / 변경1~~4) |
| ------------ | ------------------------------------------------------------------------------ |
| 계획목표년도 | `planYrDt1planYrDt4` ~~/~~ `planYr` ~~/~~ `planYrDc1planYrDc4` (+ `년` 접미사) |
- 헤더: 당초(1~~4단계) · 기준 · 변경(1~~4단계)
- 데이터 소스: `getNowDmgMtncGnsvy` 응답 배열의 **첫 번째** 요소
- 읽기 전용
---
## 7. 영역 D — 처리구역별 현황
### 7-1. 행 정의 (표시명 → Field 접두사)
각 행은 `Dt1Dt4` ~~/ (접미사 없음=기준) /~~ `Dc1Dc4` 셀로 표시한다. 숫자는 천단위 콤마, 없으면 `-`.
| 표시명 | Field 접두사 | 비고 |
| ------------------------ | -------------------- | ---------------------------------------------- |
| 하수처리인구(인) | `swtrmtPpltnCnt` | |
| 자연적하수처리인구수(인) | `natrSwtrmtPpltnCnt` | |
| 사회적하수처리인구수(인) | `scilSwtrmtPpltnCnt` | |
| 하수처리구역면적 | `swtrmtZoneArea` | |
| 물사용량원단위 | `wtrUsqtyOrgnUnit` | |
| 오수전환율(%) | `drtwSwtcRt` | |
| 변동부하율(%) | `flctnLodRt` | |
| 오수원단위 | `drtwOrgnUnit` | |
| 계획하수량합계(㎥/일) | `planSwageQntSum` | |
| 생활오수(㎥/일) | `lvngDrtwSum` | |
| 지하수(㎥/일) | `ugwtSum` | |
| 공장폐수(㎥/일) | `fctryWwtSum` | |
| 관광오수(㎥/일) | `toursmDrtwSum` | |
| 군부대(㎥/일) | `mtbsSum` | |
| 연계처리수(㎥/일) | `linkPrcsCntSum` | |
| 기타오수(㎥/일) | `etcDrtwSum` | |
| 지하수사용량(㎥/일) | `ugwtUseSum` | |
| 시설용량(㎥/일) | `fcltCpctSum` | |
| 배제방식 | `exclsMethExpln` | 문자열; 단계별 `*Dt*`/`*Dc*` 존재 시 동일 규칙 |
헤더 3행째 연도: 영역 C와 동일하게 `planYrDt*` / `planYr` / `planYrDc*` 표시.
---
## 8. API/DTO 명세
### 8-1. 공통
| 항목 | 값 |
| --------------- | ---------------------------------------------------- |
| DC Base | `/dc-service/api/v1` (`ENDPOINT.DC_SERVICE`) |
| AS adstdstr | `/as-service/api/v1/adstdstr` |
| 클라이언트 모듈 | `@/entities/dc` (`dcService`), `@/entities/adstdstr` |
> 영역 A 처리시설은 `bns-if`를 사용하지 않음.
### 8-2. 시도·구군 콤보 (non-DC)
| 기능 | Method | URL | Request DTO | Response DTO | API |
| -------- | ------ | ----------------------------------- | ------------------------ | -------------------------------- | ---------------------------------------------------------- |
| 시도목록 | GET | `/as-service/api/v1/adstdstr/sido` | — | `CmCdAdstDstrComboResponseDto[]` | `cmCdAdstDstrService.findSidoCombo` / `useFindSidoCombo` |
| 구군목록 | GET | `/as-service/api/v1/adstdstr/gugun` | Query: `sido` (시도코드) | `CmCdAdstDstrComboResponseDto[]` | `cmCdAdstDstrService.findGugunCombo` / `useFindGugunCombo` |
### 8-3. 연도·처리시설 계층 조회 — DC
| 기능 | Method | URL | Request DTO | Response DTO | API |
| --------------- | ------ | -------------------------------------- | ---------------------------- | -------------------------------- | --------------------------------------------------------- |
| 계층형 계획목록 | GET | `/dc-service/api/v1/lvNowDmgMtncGnsvy` | `LvNowDmgMtncGnsvySearchDto` | `LvNowDmgMtncGnsvyResponseDto[]` | `dcService.getLvNowDmgMtncGnsvy` / `useLvNowDmgMtncGnsvy` |
연도 Select·처리시설 Select는 **각각** `useLvNowDmgMtncGnsvy`를 호출한다 (`level`만 다르게).
**Request (**`LvNowDmgMtncGnsvySearchDto`**)**
| 필드 | 시도(`level=1`) | 구군→연도(`level=2`) | 연도→시설(`level=3`) |
| --------- | --------------- | -------------------- | -------------------- |
| `level` | `"1"` | `"2"` | `"3"` |
| `ctpvNm` | 필수 | 필수 | 필수 |
| `sggNm` | — | 필수 | 필수 |
| `gnsvyYr` | — | — | 필수 |
**Response 노드 필드 (**`LvNowDmgMtncGnsvyResponseDto`**, 부분 optional)**
| 필드 | 등장 level | 설명 |
| -------------- | ---------- | ------------------------ |
| `level` | 1~3 | 계층 깊이 |
| `ctpvNm` | 1 | 시도명 |
| `sggNm` | 2 | 구군명 |
| `gnsvyYr` | 3 | 연도 → 연도 Select value |
| `swtrmtFcltCd` | 시설(리프) | 처리시설 코드 |
| `swtrmtFcltNm` | 시설(리프) | 처리시설명 |
| `children` | 1~3 | 하위 노드 배열 |
> **TBD**: 현재 `useLvNowDmgMtncGnsvy` `enabled`가 `level && ctpvNm && sggNm && gnsvyYr`로 고정되어 `level=1`/`2` 호출이 막힘. level별 필수 파라미터만 검사하도록 수정 필요.
>
> 예: `level=1` → `ctpvNm` / `level=2` → `ctpvNm && sggNm` / `level=3` → `ctpvNm && sggNm && gnsvyYr`
### 8-4. 기본계획 수치 조회 — DC
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ---------------- | ------ | ------------------------------------ | -------------------------- | ------------------------------ | ----------------------------------------------------- |
| 현재·단계별 수치 | GET | `/dc-service/api/v1/nowDmgMtncGnsvy` | `NowDmgMtncGnsvySearchDto` | `NowDmgMtncGnsvyResponseDto[]` | `dcService.getNowDmgMtncGnsvy` / `useNowDmgMtncGnsvy` |
**Request (**`NowDmgMtncGnsvySearchDto`**)**
| 필드 | 매핑 |
| -------------- | ----------------------- |
| `swtrmtFcltCd` | 영역 A 처리시설 |
| `ctpvNm` | 영역 A 시도 표시명 |
| `sggNm` | 영역 A 구군 표시명 |
| `targetYear` | 영역 A 연도 (`gnsvyYr`) |
**Response**: 지표 필드 + `Dt1Dt4` ~~/~~ `Dc1Dc4` 접미사 스테이지 필드 (`NowDmgMtncGnsvyResponseDto`). UI는 `data[0]` 사용.
### 8-5. 분류체계 — DC
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ------------ | ------ | ------------------------------ | -------------------- | ------------------------ | ----------------------------------------- |
| 분류트리조회 | GET | `/dc-service/api/v1/clsfSystm` | `ClsfSystmParamsDto` | `ClsfSystmResponseDto[]` | `dcService.getClsfSystm` / `useClsfSystm` |
**Request**: `clsfSystmNm?`, `upClsfSystmSn?`
**Response**: `clsfSystmSn`, `upClsfSystmSn`, `clsfSystmNm`, `sortSeq`, `droppAble`, `path`
> 레거시 경로 `/clsf` → 현재 클라이언트는 `/clsfSystm`.
### 8-6. 파일 목록·처리·다운로드 — DC
| 기능 | Method | URL | Request DTO / Params | Response DTO | API |
| ------------ | ------ | --------------------------------- | ------------------------------------------------------- | ----------------- | -------------------------------------------- |
| 파일목록 | GET | `/dc-service/api/v1/file` | `FileParamsDto` (`sort?`, `clsfSystmSn?`, `vetPrcsYn?`) | `unknown` **TBD** | `dcService.getFile` / `useFile` |
| 파일처리정보 | GET | `/dc-service/api/v1/fileprcs` | Query: `fileSn` | `unknown` **TBD** | `dcService.getFilePrcs` / `useFilePrcs` |
| 파일다운로드 | POST | `/dc-service/api/v1/fileDownload` | `FileDownloadParamsDto` (`filePath?`, `fileName?`) | `unknown` **TBD** | `dcService.fileDownload` / `useFileDownload` |
> **TBD**: 파일 관련 Response 스키마·다운로드 blob/헤더 처리·`fileDownload` POST body vs query 전달 방식은 백엔드/게이트웨이 확인 후 확정.
---
## 9. UI 구현 가이드
### 9-1. 기술 스택·원칙
- UI: `@common/components` — `Dialog`, `Select`, `Button`, `Table`, `Skeleton`
- 상태: Dialog 로컬 state + React Query (`entities/dc`, `adstdstr`)
- 영역 A 연도·처리시설: `useLvNowDmgMtncGnsvy`를 level별 **두 번**(또는 시도 포함 시 세 번) 호출
- 테이블: 읽기 전용 고정 행 → `@tanstack/react-table` / `DataTable` **불필요**
- 트리: jsTree 미사용 — 재귀 들여쓰기 트리
- 번들: `PreviousPlanLookupDialog`는 outline에서 `next/dynamic` 로드 권장
- 부모 폼: **수치 미반영** (조회·다운로드 only)
- 1차 구현 시 DC 미연동이면 `@faker-js/faker` mock + TODO → 이후 `dcService` 훅으로 교체
### 9-2. 앱·파일 배치
| 구분 | 경로 |
| ------------ | ------------------------------------------------------------------------ |
| 진입 버튼 | `pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx` |
| Dialog | `pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx` |
| 필터 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-search-filters.tsx` |
| 파일 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-file-section.tsx` |
| 총설 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-summary-table.tsx` |
| 처리구역 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-area-table.tsx` |
| 셀 매핑 유틸 | `pages/sewer-bsc-plan/lib/previous-plan-table-utils.ts` |
| mock(선택) | `pages/sewer-bsc-plan/lib/previous-plan-mock.ts` |
| DC entity | `entities/dc/api/dc-service.ts`, `queries.ts`, `model/dc-interface.ts` |
| 레거시 참조 | `adssNewOld/.../cmmn/bscPlnPopup.jsp` |
### 9-3. 영역별 UI 매핑
| 영역 | 컴포넌트 | 데이터 |
| ---- | ------------------------------ | -------------------------------------------------------------- |
| A | `previous-plan-search-filters` | adstdstr + `useLvNowDmgMtncGnsvy`(level 1·2·3) |
| B | `previous-plan-file-section` | `useClsfSystm` + `useFile` + `useFilePrcs` + `useFileDownload` |
| C | `previous-plan-summary-table` | `useNowDmgMtncGnsvy` / 조회 결과 state |
| D | `previous-plan-area-table` | 동일 |
### 9-4. 다이얼로그·라우팅
| 항목 | 내용 |
| ------------ | ------------------------------------------------------------------------ |
| 다이얼로그명 | `PreviousPlanLookupDialog` / 타이틀 `기본 계획 찾기` |
| 오픈 | 사업개요 `[이전기본계획조회]` |
| 라우트 | 없음 (모달). 부모: `/sewer-bsc-plan/regist`, `/sewer-bsc-plan/[id]/edit` |
| 선택 반영 | 없음 |
### 9-5. 데이터 흐름 (구현 요약)
```
BEGIN
ON [이전기본계획조회] CLICK DO
OPEN PreviousPlanLookupDialog (sido/gugun 프리필)
END ON
ON 시도 CHANGE DO
CALL findGugunCombo
CALL getLvNowDmgMtncGnsvy(level=1)
RESET 연도·처리시설
END ON
ON 구군 CHANGE DO
CALL getLvNowDmgMtncGnsvy(level=2) // → 연도 옵션
CALL getClsfSystm
RESET 처리시설
END ON
ON 연도 CHANGE DO
CALL getLvNowDmgMtncGnsvy(level=3) // → 처리시설 옵션
END ON
ON [조회] CLICK DO
VALIDATE
CALL getNowDmgMtncGnsvy
DISPLAY 영역 C · 영역 D
END ON
ON 트리 노드 / 파일 CLICK DO
CALL getFile → getFilePrcs → fileDownload // Response TBD
END ON
END
```
---
## 10. 미확정 (TBD) 추적
| ID | 항목 | 비고 |
| --- | -------------------------------------------------------- | -------------------------------------------------------------------- |
| T1 | `useLvNowDmgMtncGnsvy` enabled을 level별 필수값으로 분기 | `1`: ctpvNm / `2`: +sggNm / `3`: +gnsvyYr |
| T2 | `getFile` / `getFilePrcs` / `fileDownload` Response DTO | 현재 `unknown` |
| T3 | 파일 다운로드 UX (blob, Content-Disposition, 게이트웨이) | 레거시 apigateway 직접 호출과 차이 |
| T4 | `clsfSystm`의 `upClsfSystmSn` 점진 로드 여부 | flat 목록 vs 계층 API |
| T5 | 1차 mock vs 즉시 `dcService` 실연동 | 마이그레이션 플랜은 mock 우선, entity는 준비됨 |
| T6 | 부모 폼 수치 반영 | 요구 없음(레거시 동일). 요청 시 별도 스펙 |
| T7 | `level=1` 응답 UI 활용 여부 | 시도 선택 시 호출하나 구군은 adstdstr — 응답은 캐시·검증용일 수 있음 |
@@ -0,0 +1,370 @@
# 화면설계서: 검토인력관리
## 1. 기본 정보
| 항목 | 내용 |
| ---------- | -------------------------------------------- |
| 버전 | 0.2 |
| 프로세스명 | UI설계 |
| 화면명 | 검토인력관리 |
| 화면 유형 | [v] 조회 [v] 입력 [ ] 수정 [v] 삭제 [ ] 출력 |
| 메뉴 경로 | 디지털행정지원 > 사이트관리 > 검토인력관리 |
---
## 2. 화면 목적
조직도에서 **한 명의 사용자**를 선택한 뒤, 결재 단계(접수·검토·승인·최종승인)별 **인원 배치** 영역에 즉시 등록한다. 등록·목록 갱신은 별도 저장 버튼 없이 **[선택 인원 배치]** 클릭 시 `rvw-mnpw` API로 바로 반영한다. 좌측 조직도는 CMS 조직·사용자 트리 API를, 우측 배치 목록은 검토인력관리 API를 사용한다.
---
## 3. 화면 구성
### 3-1. 전체 레이아웃
```
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 페이지 타이틀: 검토인력관리 브레드크럼: 디지털행정지원 > 사이트관리 > 검토인력관리 │
├─────────────────────────────────┬──────────────────────────────────────────────────────────────────┤
│ [영역 A] 조직도 │ [영역 B] 인원 배치 (결재구분별 4블록, 세로 스택) │
│ 제목: 조직도 │ │
│ [A-1] 검색: │ ┌─ [B-1] 접수 ───────────── count ─ [선택 인원 배치] ─┐ │
│ [성명·부서 검색] [조회][초기화]│ │ (배치 목록 또는 빈 상태 문구) │ │
│ 선택 안내: │ └──────────────────────────────────────────────────────┘ │
│ "선택된 인원: {이름} │ ┌─ [B-2] 검토 ───────────── count ─ [선택 인원 배치] ──┐ │
│ ({부서} / {직급})" │ └──────────────────────────────────────────────────────┘ │
│ [A-2] 조직 트리 + 사용자 행 │ ┌─ [B-3] 승인 ───────────── count ─ [선택 인원 배치] ──┐ │
│ ┌────────────────────┐ │ └──────────────────────────────────────────────────────┘ │
│ │ 조직 트리 + 사용자 │ │ ┌─ [B-4] 최종승인 ───────── count ─ [선택 인원 배치] ──┐ │
│ │ (단일 선택 강조) │ │ └──────────────────────────────────────────────────────┘ │
│ └────────────────────┘ │ │
└─────────────────────────────────┴──────────────────────────────────────────────────────────────────┘
```
### 3-2. 화면 데이터 흐름
```
BEGIN
// 최초 진입
CALL userOgnzService.findOgnzUserTree()
DISPLAY 조직도 트리 (영역 A)
CALL rvwMnpwMngService.findPage({ size: 충분히 큰 값 }) // 또는 atrzSeComCd별 분할 조회
GROUP BY atrzSeComCd
DISPLAY 인원 배치 4블록 (영역 B-1 ~ B-4) + 건수 뱃지
// 영역 A-1: 조직도 검색 (클라이언트 필터 — 서버 API 없음)
ON [조회] 버튼 CLICK / 검색 폼 SUBMIT DO
SET displayTree = filterOgnzUserTree(원본트리, keyword)
SET expandedOgnzSnIds = collectExpandedOgnzSnIds(displayTree) // 매칭 노드 자동 펼침
DISPLAY 필터된 트리 (영역 A-2)
END ON
ON [초기화] 버튼 CLICK DO
RESET keyword = ""
RESET displayTree = 원본트리
RESET expandedOgnzSnIds = 빈 Set (또는 기본 접힘)
DISPLAY 전체 트리 (영역 A-2)
END ON
// 영역 A-2: 사용자 단일 선택
ON 조직도 사용자 행 CLICK DO
VALIDATE 동일 행 재클릭이 아닌 경우만 변경
SET selectedPerson = { userUuid, userNm, ognzSn, ognzNm, jbgdSeCd, ... }
DISPLAY "선택된 인원: {userNm} ({ognzNm} / {직급명})"
END ON
// 영역 B-k: 선택 인원 즉시 배치
ON [B-k] [선택 인원 배치] 버튼 CLICK DO
VALIDATE selectedPerson 존재
SET targetAtrzSeComCd = 블록 k 에 매핑된 결재구분 코드 (§4-2 표)
CALL rvwMnpwMngService.save({
atrzSeComCd: targetAtrzSeComCd,
ognzSn: selectedPerson.ognzSn,
userUuid: selectedPerson.userUuid
})
ON 성공 DO
CALL rvwMnpwMngService.findPage (또는 해당 블록 목록만 갱신)
DISPLAY [B-k] 배치 목록·건수 뱃지 갱신
TOAST "배치되었습니다."
END ON
ON 실패(이미 존재) DO
TOAST (백엔드 메시지 연동)
END ON
END ON
// 영역 B-k: 배치 인원 제거 (UI 상 삭제 UX — 스크린샷 미표기, API는 제공됨)
ON 배치 인원 [삭제] / [제거] CLICK DO
CONFIRM "해당 인원을 배치에서 제거하시겠습니까?"
CALL rvwMnpwMngService.delete(atrzSeComCd, ognzSn, userUuid)
CALL rvwMnpwMngService.findPage
DISPLAY [B-k] 목록·뱃지 갱신
END ON
END
```
---
## 4. 영역 A — 조직도
### 4-1. 표시·상호작용
| 요소 | 내용 |
| ---------------- | --------------------------------------------------------------------- |
| 섹션 제목 | 조직도 |
| 검색 폼 (A-1) | 성명·부서 키워드 입력 + **[조회]** + **[초기화]** — §4-3 |
| 선택 안내 문구 | `선택된 인원: {userNm} ({ognzNm} / {직급표시})` — 미선택 시 안내 문구 |
| 트리 (A-2) | 본부 → 팀 계층 + 팀 소속 사용자 목록 (`displayTree` 기준 표시) |
| 선택 규칙 | **사용자 1명만** 선택 가능 (라디오형 단일 선택) |
| 선택 시각 피드백 | 선택된 사용자 행 강조(배경·테두리) |
| 로딩/오류/빈값 | Skeleton / 오류 문구 / "등록된 조직/사용자가 없습니다." |
| 검색 결과 없음 | 필터 후 매칭 노드·사용자 0건. "조회된 인원이 없습니다." 문구 노출 |
### 4-2. 조직도 데이터 매핑
| UI 표시 | 트리 DTO 필드 (`OgnzUserTreeResponseDto`) | 비고 |
| --------------- | ----------------------------------------- | ----------------------------------------- |
| 조직명 | `ognzNm` | 접기/펼치기 노드 |
| 사용자명 | `users[].userNm` | 클릭 시 선택 |
| 부서(안내 문구) | `ognzNm` (소속 팀 노드) | 선택 시 `selectedPerson.ognzNm` |
| 직급(안내 문구) | `users[].jbgdSeCd` 또는 `jbpsSeCd` | 공통코드 라벨 변환 |
| 저장용 조직 SN | `ognzSn` | 사용자가 속한 **리프/팀 노드**의 `ognzSn` |
| 저장용 사용자 | `users[].userUnqId` | API body `userUuid`로 전달 (§6-3) |
### 4-3. 조직도 검색 (영역 A-1)
결재선 관리 `AprvrPickerDialog`와 동일한 **클라이언트 사이드 필터** 패턴을 사용한다. 별도 검색 API는 없으며, `useOgnzUserTree`로 받은 **원본 트리**를 `filterOgnzUserTree`로 가공해 `displayTree`에 반영한다.
#### 필드 정의
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
| -------------- | --------- | ---- | ------ | ----------------- | ----------------------------------------- |
| 성명·부서 검색 | Text | N | (빈값) | — (클라이언트만) | `placeholder="성명·부서 검색"` |
| 조회 | Button | — | — | — | `type="submit"` — 폼 제출 시 필터 적용 |
| 초기화 | Button | — | — | — | `type="button"` — 키워드·트리·펼침 초기화 |
#### 필터 규칙 (`filterOgnzUserTree`)
| 대상 | 매칭 조건 |
| ---------------- | ----------------------------------------------------- |
| 조직명 | `ognzNm`에 키워드 포함 (대소문자 무시) |
| 사용자명 | `userNm`에 키워드 포함 (대소문자 무시) |
| 조직 매칭 시 | 해당 노드 **소속 사용자 전원** 유지 |
| 사용자만 매칭 시 | 매칭 사용자만 남기고 상위 조직 노드는 유지(경로 보존) |
| 하위 조직 | 재귀 필터 — 자식·손자 노드 매칭 시 상위도 표시 |
| 키워드 빈값 | 원본 트리 전체 반환 |
#### 조회 후 트리 펼침 (`collectExpandedOgnzSnIds`)
- **[조회]** 성공 후 필터 결과 트리의 **모든 노드** `ognzSn`을 `expandedOgnzSnIds`에 넣어 자동 펼침
- 검색 결과에서 사용자·하위 조직을 바로 확인할 수 있도록 함
#### 초기화 동작
| 상태 | 초기화 후 값 |
| ------------------- | --------------------------------------- |
| `keyword` | `""` |
| `displayTree` | `useOgnzUserTree` 원본 `data` |
| `expandedOgnzSnIds` | `new Set()` (전체 접힘) |
| `selectedPerson` | **유지** — 검색 초기화와 선택 해제 분리 |
#### 버튼
| 버튼명 | 동작 |
| ------ | --------------------------------------------------------- |
| 조회 | `filterOgnzUserTree(data, keyword)` → `displayTree` 갱신 |
| 초기화 | 키워드·표시 트리·펼침 상태 리셋 (§3-2 `ON [초기화]` 블록) |
---
## 5. 영역 B — 인원 배치
### 5-1. 블록 공통
| 요소 | 내용 |
| ------------------ | ------------------------------------------------- |
| 블록 제목 | 접수 / 검토 / 승인 / 최종승인 |
| 건수 뱃지 | 해당 `atrzSeComCd`에 배치된 인원 수 |
| 액션 버튼 | **[선택 인원 배치]** — 클릭 시 즉시 `save` (§3-2) |
| 빈 상태 | "배치된 인원이 없습니다." |
| 배치 목록(있을 때) | 인원명·부서·직급 등 |
### 5-2. 결재구분 ↔ 블록 매핑
| 블록 | UI 제목 | `atrzSeComCd` | 비고 |
| ---- | -------- | ------------- | -------------------------------------------------------------- |
| B-1 | 접수 | `ATRZ00` | 결재선 관리 `ATRZ_RECEIVER_CD`와 동일 |
| B-2 | 검토 | `ATRZ01` | 결재선 `ATRZ_REVIEWER_1_CD`. `ATRZ02` 별도 블록 여부 — **TBD** |
| B-3 | 승인 | `ATRZ03` | 결재선 `ATRZ_APPROVER_CD` |
| B-4 | 최종승인 | `ATRZ99` | 결재선 `ATRZ_FINAL_CD` |
공통코드 마스터 `ATRZSE` / 화면 셀렉트용 `ATRZSECD`와 정합성은 배포 환경 코드표로 최종 확인.
### 5-3. 배치 목록 항목 (표시)
| 표시명 | Field (응답 DTO) | 비고 |
| -------- | ---------------- | ---------------------------- |
| 사용자명 | `userUuidNm` | `@CodeName` 미적용 시 TBD |
| 결재구분 | `atrzSeComCdNm` | 블록 제목과 중복 — 생략 가능 |
| 조직 SN | `ognzSn` | 삭제 API path에 사용 |
| 사용자 | `userUuid` | 삭제 API path에 사용 |
### 5-4. 버튼
| 버튼명 | 위치 | 동작 |
| -------------- | ----------- | --------------------------------- |
| 선택 인원 배치 | 각 B-k 헤더 | `save` 후 목록·뱃지 갱신 (§3-2) |
| 제거 | 목록 항목 | `delete` 후 목록·뱃지 갱신 (§3-2) |
### 5-5. 검증·제약
| 규칙 | 내용 |
| ------------- | ------------------------------------------------------------------- |
| 선택 필수 | `[선택 인원 배치]` 시 `selectedPerson` 없으면 버튼 비활성 또는 안내 |
| 단일 선택 | 조직도에서 사용자 1명만 `selectedPerson` 유지 |
| 중복 등록 | 동일 PK `(atrzSeComCd, ognzSn, userUuid)` 재등록 시 서버 오류 |
| 다건 배치 | 동일 블록에 서로 다른 사용자·조직은 **복수 행** 가능 (PK가 다름) |
| 결재구분 변경 | 본 화면 주 흐름은 `save`만 |
---
## 6. API/DTO 명세
### 6-1. 공통
| 항목 | 내용 |
| ------------- | ------------------------------------------------------------- |
| 인증 | JWT (`Authorization` 헤더) |
| 페이징 | Spring `Pageable`: `page`, `size`, `sort` → `PageResponse<T>` |
| 프론트 엔티티 | `@/entities/user-organization`, `@/entities/rvw-mnpw` |
### 6-2. 조직·사용자 트리 (영역 A) — `user-ognz`
| 항목 | 내용 |
| ----------- | ------------------------------- |
| 서비스 | `cms-service` |
| Base prefix | `/cms-service/api/v1/user-ognz` |
| 기능 | Method | URL | Request DTO | Response DTO | API |
| ---------------- | ------ | ----------- | ----------- | --------------------------- | ---------------------------------- |
| 조직·사용자 트리 | GET | `/treeuser` | — | `OgnzUserTreeResponseDto[]` | `userOgnzService.findOgnzUserTree` |
**`OgnzUserTreeResponseDto`**: `ognzSn`, `upOgnzSn`, `ognzNm`, `users[]`, `children[]`
**`OgnzUserTreeUserDto`**: `userUnqId`, `userNm`, `jbpsSeCd`, `jbgdSeCd`
**React Query**: `useOgnzUserTree`
### 6-3. 검토인력관리 (영역 B) — `rvw-mnpw`
| 항목 | 내용 |
| ----------- | ------------------------------------- |
| 서비스 | `as-service` |
| Base prefix | `/as-service/api/v1/rvw-mnpw` |
| ENDPOINT | `ENDPOINT.AS_COMMON_SERVICE.RVW_MNPW` |
| 기능 | Method | URL | Request DTO / Query | Response DTO | API |
| --------------- | ------ | ------------------------------------ | -------------------------------- | ----------------------------- | ---------------------------- |
| 목록 조회 | GET | `/` | `RvwMnpwMngSearchDto` + Pageable | `Page<RvwMnpwMngResponseDto>` | `rvwMnpwMngService.findPage` |
| 상세 조회 | GET | `/{atrzSeComCd}/{ognzSn}/{userUuid}` | Path | `RvwMnpwMngResponseDto` | `rvwMnpwMngService.findById` |
| 등록(즉시 배치) | POST | `/` | `RvwMnpwMngSaveRequestDto` | `RvwMnpwMngResponseDto` (201) | `rvwMnpwMngService.save` |
| 결재구분 변경 | PUT | `/{atrzSeComCd}/{ognzSn}/{userUuid}` | `RvwMnpwMngUpdateRequestDto` | `RvwMnpwMngResponseDto` | `rvwMnpwMngService.update` |
| 삭제 | DELETE | `/{atrzSeComCd}/{ognzSn}/{userUuid}` | Path | `string` | `rvwMnpwMngService.delete` |
**`RvwMnpwMngSearchDto`**: `atrzSeComCd?`, `ognzSn?`, `userUuid?` + `page`, `size`, `sort`
**`RvwMnpwMngSaveRequestDto`**: `atrzSeComCd*`, `ognzSn*`, `userUuid*`
**`RvwMnpwMngUpdateRequestDto`**: `newAtrzSeComCd*` — 본 화면 기본 흐름에서는 미사용
**`RvwMnpwMngResponseDto`**: `atrzSeComCd`, `atrzSeComCdNm?`, `ognzSn`, `userUuid`, `userUuidNm?`, `rgtr?`, `regDt?`
### 6-4. 조직도 ↔ 검토인력 필드 매핑 (저장 시)
| 조직도 (`OgnzUserTreeUserDto` + 노드) | `RvwMnpwMngSaveRequestDto` | 비고 |
| ------------------------------------- | -------------------------- | --------------------- |
| (블록 k의 `atrzSeComCd`) | `atrzSeComCd` | §5-2 표 |
| 소속 팀 `ognzSn` | `ognzSn` | |
| `userUnqId` | `userUuid` | 문자열(UUID)로 직렬화 |
---
## 7. UI 구현 가이드
### 7-1. 기술 스택·원칙
- **공통 컴포넌트**: `@common/components` (`Button`, `Badge`, `Card` 또는 섹션 레이아웃)
- **데이터**: React Query — `useOgnzUserTree`, `useRvwMnpwMngPage`, `useSaveRvwMnpwMng`, `useDeleteRvwMnpwMng`
- **상태**: `selectedPerson`(단일), `keyword`, `displayTree`, `expandedOgnzSnIds`, 블록별 배치 목록(서버 데이터 파생)
- **조직도 검색**: `filterOgnzUserTree`, `collectExpandedOgnzSnIds` (`widgets/ognz-user-tree/lib` 또는 페이지 `lib`)
- **저장 UX**: 별도 화면 저장 없음 — **[선택 인원 배치]** = `save` mutation 즉시 호출
- **조직도 UI**: 결재선 관리 `ognz-user-tree.tsx`를 **widgets**로 승격해 공통화
### 7-2. 모듈·파일 배치(제안)
| 구분 | 경로 |
| -------- | ---------------------------------------------------------------------------------- |
| 라우트 | `apps/as/app/(with-base-layout)/rvw-mnpw-mng/page.tsx` |
| 페이지 | `apps/as/src/pages/rvw-mnpw-mng/` |
| UI | `.../ui/rvw-mnpw-mng-page.tsx`, `rvw-mnpw-assignment-panel.tsx` 등 |
| model | `.../model/selected-ognz-user.ts`, `rvw-mnpw-block-config.ts` (`atrzSeComCd` 상수) |
| lib | `.../lib/group-rvw-mnpw-by-atrz-se.ts` (목록 → B-1~B-4 분배) |
| **위젯** | `apps/as/src/widgets/ognz-user-tree/` — 아래 이전 대상 |
| 엔티티 | `entities/user-organization/`, `entities/rvw-mnpw/` (구현 완료) |
#### widgets/ognz-user-tree 이전 (결재선 → 공통)
| 현재 (결재선 관리) | 이전 후 (widgets) |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| `pages/approval-line-management/ui/ognz-user-tree.tsx` | `widgets/ognz-user-tree/ui/ognz-user-tree.tsx` |
| `pages/.../ui/ognz-user-tree-node.tsx` | `widgets/ognz-user-tree/ui/ognz-user-tree-node.tsx` |
| `pages/.../model/reviewer-option.ts` | `widgets/ognz-user-tree/model/selected-ognz-user.ts` (이름·필드 확장: `ognzSn` 필수) |
| `pages/.../lib/filter-ognz-user-tree.ts` | `widgets/ognz-user-tree/lib/filter-ognz-user-tree.ts` (**필수** — 검색 공통화) |
| `widgets/ognz-user-tree/index.ts` | `OgnzUserTree`, `OgnzUserTreeNode`, 타입 export |
결재선 `AprvrPickerDialog`는 `@/widgets/ognz-user-tree` import로 교체. 검토인력관리 페이지는 **인라인 조직도**(다이얼로그 없음)로 동일 위젯 사용.
**`SelectedOgnzUser`** — `ReviewerOption` 확장:
```typescript
export interface SelectedOgnzUser {
userUuid: string; // OgnzUserTreeUserDto.userUnqId
userNm: string;
ognzSn: number;
ognzNm: string;
jbgdSeCd?: string;
jbpsSeCd?: string;
}
```
### 7-3. 영역별 UI 매핑
| 영역 | 주요 UI |
| ---- | -------------------------------------------------------------------- |
| A-1 | 검색 폼 (`Input` + 조회/초기화) — `AprvrPickerDialog` 90~103행 패턴 |
| A-2 | `OgnzUserTree(nodes={displayTree})` + 선택 안내 + `useOgnzUserTree` |
| B-k | 제목·Badge(count)·`선택 인원 배치`·배치 목록(또는 empty) |
| 공통 | 페이지 헤더·브레드크럼·2열 레이아웃 (`grid` / `flex`, 모바일은 스택) |
### 7-4. 다이얼로그·라우팅
| 항목 | 내용 |
| ---------- | ----------------------------------- |
| 다이얼로그 | **없음** (조직도 인라인, 즉시 저장) |
| 라우팅 | 메뉴 `검토인력관리` 단일 페이지 |
### 7-5. 데이터 흐름(구현 요약)
- **진입**: `useOgnzUserTree` → `displayTree = data` + `useRvwMnpwMngPage({ page: 0, size: N })` → 4패널.
- **검색**: `keyword` 로컬 상태 → **[조회]** 시 `filterOgnzUserTree` + `collectExpandedOgnzSnIds` → `OgnzUserTree`에 `displayTree` 전달.
- **초기화**: `keyword`·`displayTree`·`expandedOgnzSnIds` 리셋 (`selectedPerson` 유지).
- **선택**: 트리 `onSelect` → `selectedPerson` (반드시 `ognzSn` 포함).
- **배치**: `useSaveRvwMnpwMng().mutate({ atrzSeComCd, ognzSn, userUuid })` → `onSuccess`에서 `rvw-mnpw` query invalidate.
- **삭제**: 목록 항목에서 `useDeleteRvwMnpwMng` — UI 확정 후 연결.
### 7-6. 미확정 / 검증 필요
- [ ] `ATRZ02`(검토2) 별도 블록 필요 여부
- [ ] 권한(역할별 CRUD)
+45 -8
View File
@@ -1,8 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
import { PORT, PROXY_HOST, SERVER_API_URL, SITE_ID } from "@/shared/config";
import {
AUTH_USER_ID,
CLAIM_NAME,
PORT,
PROXY_HOST,
SERVER_API_URL,
SITE_ID,
} from "@/shared/config";
const serverApiUrl = process.env.SERVER_API_URL ?? "http://localhost:3000";
const serverApiUrl = process.env.SERVER_API_URL;
const dcServiceApiUrl = process.env.DC_SERVICE_API_URL;
// CSP 헤더 설정 함수 (img-src만)
function addSecurityHeaders(response: NextResponse): NextResponse {
@@ -22,7 +30,15 @@ export function middleware(req: NextRequest) {
if (pathname.startsWith("/server")) {
const queryParams = searchParams.toString();
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
let serverUrl: string | undefined;
if (pathname.includes("/dc-service")) {
serverUrl = dcServiceApiUrl;
} else {
serverUrl = serverApiUrl;
}
const newUrl = `${serverUrl}${pathname.replace("/server", "")}${
queryParams ? `?${queryParams}` : ""
}`;
@@ -34,10 +50,31 @@ export function middleware(req: NextRequest) {
return addSecurityHeaders(response);
}
// if (pathname === "/") {
// const response = NextResponse.redirect(new URL("/dashboard", req.url));
// return addSecurityHeaders(response);
// }
// tus-js-client PATCH 요청: Location 헤더로 /server 없이 반환된 경로 직접 처리
// (예: /attach-service/api/v1/tus/upload/{id})
if (pathname.startsWith("/attach-service")) {
const requestHeaders = new Headers(req.headers);
const token = req.cookies.get(CLAIM_NAME)?.value;
const userId = req.cookies.get(AUTH_USER_ID)?.value;
const newUrl = `${serverApiUrl}${pathname}`;
if (token) requestHeaders.set(CLAIM_NAME, token);
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
logMessages.push(` [TUS Proxy] Rewritten URL: ${newUrl}`);
logMessages.push("\n--------------------------------\n");
console.log(logMessages.join("\n"));
const response = NextResponse.rewrite(new URL(newUrl), {
headers: requestHeaders,
});
return addSecurityHeaders(response);
}
if (pathname === "/") {
const response = NextResponse.redirect(new URL("/dashboard", req.url));
return response;
}
logMessages.push("\n--------------------------------\n");
console.log(logMessages.join("\n"));
@@ -47,5 +84,5 @@ export function middleware(req: NextRequest) {
}
export const config = {
matcher: ["/server/:path*", "/"],
matcher: ["/server/:path*", "/attach-service/:path*", "/"],
};
-6
View File
@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+45
View File
@@ -1,8 +1,53 @@
import type { NextConfig } from "next";
const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000";
const webhwpCtrlBase = (
process.env.NEXT_PUBLIC_WEBHYP_CTRL_URL || "http://hwp.onasys.co.kr/webhwpctrl"
).replace(/\/$/, "");
const webhwpAssetPrefixes = ["js", "hwpsdk", "dist", "commonFrame", "wasm"] as const;
const nextConfig: NextConfig = {
output: "standalone",
reactStrictMode: true,
transpilePackages: ["@common/assets"],
experimental: {
globalNotFound: true,
},
async rewrites() {
return {
beforeFiles: [
{
source: "/_next/static/:path*",
destination: `${subDomain}/_next/static/:path*`,
has: [
{
type: "header",
key: "referer",
value: "(.*)/cms(.*)",
},
],
},
],
afterFiles: [
{
source: "/cms/:path*",
destination: `${subDomain}/cms/:path*`,
},
...webhwpAssetPrefixes.map((prefix) => ({
source: `/webhwpctrl/${prefix}/:path*`,
destination: `${webhwpCtrlBase}/${prefix}/:path*`,
})),
{
source: "/webhwpctrl/file/:path*",
destination: `${webhwpCtrlBase}/file/:path*`,
},
],
fallback: [],
};
},
};
export default nextConfig;
+14 -3
View File
@@ -3,29 +3,40 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"lint:fsd": "steiger ."
},
"dependencies": {
"@common/assets": "workspace:*",
"@common/auth": "workspace:*",
"@common/components": "workspace:*",
"@common/network": "workspace:*",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@faker-js/faker": "^9.9.0",
"@frontend-opensource/use-react-hooks": "^1.1.4",
"@hookform/resolvers": "^4.1.3",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-query": "^5.75.5",
"@tanstack/react-query-devtools": "^5.75.5",
"@tanstack/react-table": "^8.21.2",
"axios": "^1.8.1",
"class-variance-authority": "^0.7.1",
"dayjs": "^1.11.13",
"js-cookie": "^3.0.5",
"next": "15.3.8",
"lucide-react": "^0.474.0",
"next": "15.5.18",
"react-hook-form": "^7.54.2",
"tus-js-client": "^4.3.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@hookform/devtools": "^4.4.0",
"@types/js-cookie": "^3.0.6",
"eslint": "^9.0.0",
"eslint-config-next": "15.3.8",
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

+63
View File
@@ -0,0 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 538 755">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<!-- Generator: Adobe Illustrator 28.7.9, SVG Export Plug-In . SVG Version: 1.2.0 Build 218) -->
<g>
<g id="Layer_1">
<g id="han" data-label-shift="dx,dy">
<path class="cls-1" d="M159.2,197l-5,5.8,5.1,3.7,13.2,9.7-7.3,3.5-8.2-1.2-9.4,1.4,1.4,4.9-3.5,7.3,3.8,2.6-.9,4.7,8.9-2.3,4.2-3.7,5.2,4.2-6.1,2.3.9,12,3,7-.9,3.3-.5,1.6,5.4,6.8,5.2,13.4h-3.6c0-.1,6.4,3.8,6.4,3.8l4.8,6h1.2l3.6-5.4,3.6-.6,13.8,5.9,19.7-1.1,4.6-6.5,3-5.2,8.2-1.7,11.7-17.8,7.1,1.9,4.9,10,13.2,9.6,3.8,13-1.4,12.2-.5,9.5,1.6,1.1-.6,1.3v1.1c.1,0-2.2,2.4-2.2,2.4l-17.5,7.4,7.6,19.2,16.3,1.1,8.4,10.8,12.6.6,4.2-5.2-4.6-7.9-9.2-6.2,12.1-7.1,6.4,5.2,1.3-5.6-6.6-6.5,17.5-5.8,4.8,4.4,4.8-2.1-4.7-6.5,4.5-8.8,6.3-.4,4.7-3,8,3,24.1,4.5,11.5-7.8-2.8-6.4,6.5-11.7,10.5-5.5,7.6-7.9,10.2-2.3,13.4,4.6,4.4-8,12.4,8.2,3.4-3.1.2-15.2.4-11.3,13.4-3.2,4.6-.4-1.9,9.9,9.8,4.6,1.6,12.8,12.2,9.2,9.8-8.4,15-8.9-3.7-19-21.8-40.6-5.7-10.6,1.2-6.8-4.9-6.1-4-4.9,1.2-5.2-26.3-33.1-5.8-12.1-7.1-14.9-11.9-20.9-2.9-9.4-2.1-6.7-1.8-5.2-8.7-25.5-8.6-14.4-.9-11-5.2-11-3-6.3-6,6.5-5.2,22.8-9.8,11-5.5,2.8-7.4,3.7-17.1.5-12.7-1.6-8.2,2.3-5.2-3.4-3.4-2.2-18.8-2.3-21.8,4.5-19-3.7-3.5,4.7-11.2-7.3-5.2,4.2-6.1-.7-11.5,3.5-14.1,10.8-3.7,10.1-12.9,9.8-4,7.3-8.5,6.8-2.3,1.9-9.1,4.5-4,18.3-9.2,11.5-14.3,1.2-2.1,30,5.6,1.4,3.3,9.2-1.4,2.7.9,2.6,2.9-.7,1.8,3.2-4.5,2.2,1.7,8.6,3.8,6.9,8.9,3.4Z"/>
</g>
<g id="yeongsan" data-label-shift="dx,dy">
<path class="cls-1" d="M57.4,578.4s.2,1.4.2,2.3.7,1.6.7,1.9.5,1.4.7,2.3c.2.9,1.2,1.2,1.2,1.2h0s.5,1.4.5,1.4c0,0-.5.7-.9,1.4-.5.7,1.6.9,1.6.9l2.8-.7s1.4-.7,1.4-1.2,0,0-.2-.7c-.2-.7-.5-.7-.9-1.2-.5-.5.5-.2.5-.2l.5.2,2.6-.5s.9-.5,1.4-.7c.5-.2-.2-.5-.2-.9s-.2-1.2-.2-1.2l.2-.9v-1.2c0-.5.7-.7.9-1.2.2-.5.5-.7.9-1.2.5-.5,0-.7,0-1.6s-.5-.5-.5-.9-.5,0-.5,0l-.9.5-.9.7s-.2.9-.5,1.2c-.2.2-1.4.2-1.4.2,0,0-.2-.5-1.2-.9-.9-.5,0,0-.5,0s-1.2-.2-1.2-.2c0,0-1.2-.7-1.6-.9-.5-.2-.5-.9-.5-.9h-1.2s-1.9,1.4-2.8,1.9c-.9.5,0,1.2,0,1.2Z"/>
<path class="cls-1" d="M45.2,585.7c.2.2,0,.2.7.9.7.7.5,1.6.9,1.6s.2-.5,1.2,0c.9.5,1.4.5,1.9.7.5.2,1.6.9,1.9.9s1.4.7,1.4.9-.2.2,0,.5c.2.2,0,.5.5,1.4.5.9,1.9,2.6,1.9,2.6,0,0-.7.7.5.5,1.2-.2,1.9-.2,1.9-.9s.2-1.2.2-1.4-.2-.2-.5-1.2c-.2-.9-.7-1.6-.7-1.6,0,0,1.2-.2,0-1.2-1.2-.9-.2-1.2-1.2-.9-.9.2-1.9.2-1.9.2-.2-1.2-.2-1.9-.2-1.9l-.5-.5.7-1.4h0c.5,0,.9-.2,1.2-.2s.9-.7.9-.7c0,0,.2-.2,0-.7-.2-.5-.2-.2-.9-.7s-1.6-.7-1.6-.7l-2.1-1.2-1.4-.9c-.7-.2-2.6.9-2.6.9,0,0,.9-.5,0,.7-.9,1.2-2.3,1.9-2.3,1.9,0,0-.2.5,0,.9.2.5,0,1.2.2,1.4Z"/>
<path class="cls-1" d="M28.6,585.5s0,.7,1.2.7h2.1c.5,0,.9.7.9.7v1.9l1.2.7s1.4,0,1.9-.5c.5-.5.5-.7.5-.7l-.5-1.4,2.3-.7s.5-.5.7-1.2c.2-.7-.2-1.2,0-2.3.2-1.2,0-2.8,0-2.8,0,0,.9-1.4.9-2.1s1.9-2.6,1.9-2.8h0v-.7h-1.4c-.2,0-1.7.5-2.1.7-.5.2,0,0-.9,1.2s-.2,1.6-.2,1.6l-1.9.5-1.4.7-.9.2s-1.2-.2-2.1-.2-.5.9-.5.9c0,0-1.2,1.4-1.9,1.6-.7.2-.7.7-.7.7,0,0,.9,2.3.9,2.8v.5Z"/>
<path class="cls-1" d="M41.7,591.4c-.5.2-.7-1.2-.5,0,.2,1.2,1.2,2.1,1.2,2.1l1.4.9s.9-.2,1.4,0c.5.2.9,0,.9,0l1.9.7v.2s.7-.2.5-1.6c-.2-1.4,0-1.4,0-1.4l-.7-.9-2.6-1.4h0s-2.8-.7-3-.5c-.2.2-.5-.2-.5.2s.5,1.4,0,1.6Z"/>
<path class="cls-1" d="M34.2,597.7s0,0,0,0c0,0,0,0,0,0Z"/>
<path class="cls-1" d="M45,598.9l-.7-.7-.5-.9s.2-1.4-.2-1.4-1.9-.5-1.9-.5l-1.4-.7h0s-1.4,0-1.4,0c0,0-.9,0-1.2.7-.2.7-.9.5-.9.9s-2.1,1.2-2.5,1.4c0,0,.2.2.4.5.5.7.5,1.4,1.2,1.4s2.1.2,2.3,0c.2-.2.9-.7,1.2-.5.2.2.5.5.5.9s-.5,1.2-.5,1.6.5,0,0,.5c-.5.5.2,1.4.5,1.6.2.2-.7,2.1-.7,2.1,0,0-.5.5-.5.7s1.4.7,1.6,0c.2-.7.7-1.2.9-1.4.2-.2.9-.5.9-.5,0,0-.5.2.7.2s2.1.2,2.1-.5v-1.9s-.2-1.2,0-1.4c.2-.2,0-2.3,0-2.3Z"/>
<path class="cls-1" d="M18.5,619.7c.7,0,1.9-.9,2.3,0,.5.9,1.9,2.6,1.9,2.6,0,0,.2-.2,1.4,0,1.2.2,1.2.5,2.1,0,.9-.5,1.4-1.4,1.4-1.4l1.2-1.2,3.3-1.4s-.2-.7-.7-2.1c-.5-1.4-.7-2.1-.7-2.1,0,0,1.2.2.5-.9-.7-1.2-.9-1.9-.9-1.9l-.5-1.6h0s-.7-.9-.7-.9l-2.1.2s-.2,1.2,0,1.9c.2.7.2.9,0,1.4-.2.5-.7-.5-1.2,0-.5.5-2.3.2-2.3.2h-1.2l-.9,1.4-1.2.9-2.3.9-2.1,1.4s-.5,0,0,.9c.5.9,2.1,1.6,2.8,1.6Z"/>
<path class="cls-1" d="M33.1,624h-.9l-1.4-.7-2.1.7s-.9,1.2-.9,1.6.7,1.9,1.4,2.3c.7.5,1.6.7,1.9,1.6.2.9.2,1.9.2,1.9,0,0,1.9.7,2.3-.7.5-1.4.2-2.3.7-2.6.5-.2,1.9-1.6,1.9-1.6,0,0-.5-.5.7-.5s1.6-.5,1.6-.7v-1.9l-.9-.9h.9l.7-.9s.2-.7,0-1.2c-.2-.5-.5-1.9-.5-1.9l-2.6.5h0l-1.4,1.4-.5.9s.2.9,0,1.2c-.2.2-1.2,1.4-1.2,1.4Z"/>
<path class="cls-1" d="M65.6,620.2l1.9-.7-2.1-2.6-3.5-1.9h0s-3.3.7-3.3.7l1.6,2.1.9,1.4s-.5.5-.9.9c-.5.5-1.6.7-1.6.7,0,0-.5.5-.5.9s-.9,0-2.1.7c-1.2.7-4.5,1.9-4.5,1.9h-.9c.9,0,1.6,2.6,2.3,2.6h3.1s-.2-.5.9-.7c1.2-.2,2.6-1.2,2.6-1.2,1.4.2,2.3-.7,3,.5.7,1.2,1.4.9,1.6,1.4.2.5,1.2,2.1,1.2,2.1l-.7,2.6s1.2.9,1.6.7c.5-.2,1.9-1.4,1.9-1.4l.9-1.9s-.5-1.4,0-1.9c.5-.5-1.6-1.4-1.6-1.4l-2.8-.7-1.9-2.6v-1.2l2.8-1.2Z"/>
<path class="cls-1" d="M64.2,635.5v-1.6s-3.3,1.4-3.3,1.6.9,1.6.9,1.6h1.6l.7-1.6Z"/>
<path class="cls-1" d="M59.8,639.2c0,1.2.5,2.8,1.2,3,.7.2,1.6,0,1.6,0v-3l-1.6-.9s-1.2-.2-1.2.9Z"/>
<path class="cls-1" d="M38.4,638.3c1.2,0,2.8-4.5,2.8-4.5v-1.4h0s-3.5,0-3.5,0l-2.1,1.4-3.5.7s-.2,1.9,1.4,1.9.7-.1.6-.2c0,0,.3,0,.6.2,1.6.5,3.3.7,3.3,1.2s-.7.7.5.7Z"/>
<path class="cls-1" d="M15.4,630.1h0s0,0,0,0h0Z"/>
<path class="cls-1" d="M7,642.7c.2-.5,1.2-.7,1.2-1.4s-1.6-2.6-.5-3.1c1.2-.5,2.6-.9,2.8-1.2.2-.2,1.2-.7,1.6-.5s.9.9.9.9l1.4-.5.5-.9s.2-.9.5-1.4c.2-.5.7-1.9.7-1.9v-1.2l-.7-1.6c-.3.2-2.6,2.1-2.8,2.3-.2.2-3,1.2-3.3,1.2s-3.1.2-3.1.2l-3.8.5-2.6,1.9,1.9,2.1s-.2,1.9,0,2.1c.2.2-.5,1.2.5,1.6.9.5,1.6.9,1.6.9,0,0,2.8.2,3-.2Z"/>
<path class="cls-1" d="M16.6,649.1c0-.7-1.2-1.6-1.2-1.6l-.7-.7-.9-.9s-.2-1.2-.2-1.4-.7-.9-.7-.9c0,0-2.3.2-2.3.5s-.7.7-.7.7c0,0-.2.7-1.6.7s-2.8-.5-2.6,0c.2.5-.2.9-.2,1.6s-.5,1.9-.5,1.9v1.2s.2,1.2.7,1.4c.5.2,1.2.5,1.4.7.2.2,1.4.9,1.2,1.4-.2.5,2.3.2,2.3.2l.5-.5c.2-.2,1.9-1.2,1.9-1.2,0,0,0-.9.5-.9s3.3-.5,3.3-.5v-1.6Z"/>
<polygon class="cls-1" points="12.9 643.4 12.9 643.4 12.9 643.4 12.9 643.4"/>
<path class="cls-1" d="M27.7,639.9c.5.5,1.4,1.6,2.1,1.9.7.2,1.2.5,1.4,1.2.2.7.2,1.4.5,2.1.2.7,1.9.7,1.9,1.2s.5-.2,1.4,0c.9.2.2-.2,1.6,1.2,1.4,1.4,2.3,2.1,2.6,2.6.2.5.9,0,.9,0l1.2-.9s-.2-1.6,0-1.6.2-2.1.2-2.3-.5-2.3-.5-2.3c0,0-1.2-1.4-1.4-1.4s-2.1-.7-2.1-.7l-1.2-1.2-.9-1.2-1.4-.5h0l-3.7-.7h-2.1l-1.2,1.6s.2.7.7,1.2Z"/>
<path class="cls-1" d="M32.6,663.4c.5.2,2.3.5,2.8.5s1.6-.2,1.9-.5c.2-.2.9-.2,1.6-.2s3.5.2,3.5.2c0,0,.5-.7.2-1.4-.2-.7-.9-1.4-.9-1.4l-.2-.7-.7-.5.7-1.2v-.9s-.7-.5-1.4-.5-1.4.2-1.4-.2-.9-1.9-.9-1.9l-1.4.7h0c-.9.5-1.9.7-2.1.9-.2.2-3.3.2-3.1.7.2.5.7,1.2.9,1.4.2.2.9,1.6.9,1.6,0,0-.5,1.6-.5,1.9s-.5,1.2,0,1.4Z"/>
<path class="cls-1" d="M21.3,670.4c0-.2,0-1.4.5-1.6.5-.2.7-.5.7-.7s-.7-1.2-.7-1.4-1.2-1.2-1.2-1.2l-.5-1.2-.2-1.2-.2-1.9h0s-.9-1.9-1.2-1.9-1.4-.2-1.4,0-.5.9-.5.9v.7c0,.2-.2.7,0,1.2.2.5-.2,1.4,0,1.6.2.2.2.7.5,1.4.2.7.7,1.2.7,1.4s-.2,1.2-.2,1.2c0,0-.5-.7-.5.5s-1.9,1.9-.5,2.1c1.4.2,2.8.5,3,.5s1.6-.2,1.6-.5Z"/>
<path class="cls-1" d="M27.4,673s-.5-.9,0-1.6c.3-.5.5-.9.7-1.2.2-.5.9-.5.9-.9s.5-2.3,0-2.8c-.5-.5-.9-1.6-1.4-2.3-.5-.7-.2-.9-1.2-1.6-.9-.7-1.9-1.2-1.9-1.2l-1.2.2h0s-.7,2.1-.7,2.3.2,2.3.7,2.6c.5.2,1.6,1.4,1.9,1.9.2.5.2,1.2.2,1.6s-.7,2.1-.9,2.1,0,0-1.2.5c-1.2.5-2.6.2-3,.9-.5.7-1.2,1.4-1.2,1.4v1.4c0,.2-.2.7.5.7s2.3-.2,2.8-.5c.5-.2,1.4-.7,1.4-.7l2.3-2.1,1.2-.7Z"/>
<path class="cls-1" d="M63.7,683.1c-.2-.5-1.4-2.1-1.4-2.1l-.5-.9s0-1.6-.9-1.9c-.9-.2-3-.2-3-.2l-1.6-2.3s-1.4-2.6-1.6-2.6-1.4,0-2.1-.2c-.7-.2-2.6-1.2-2.6-1.2l-.9.7c.2.7,1.6,2.8,1.6,2.8l1.2,2.3-.5,1.9s-1.9.5-2.3.5h-3s-.5-.7-.7.7c-.2,1.4-2.6,2.6-2.6,2.6h-3s-.9,1.6-.9,2.1-6.1,3.1-6.1,3.1l-1.9,2.6-1.9.5s-.5.7-.5.9-1.2,1.6-1.2,1.6h-.7s-2.3.5-2.3.9.5,1.6.5,2.1v1.6s0,1.6.2,1.6,1.4.7,1.4.7l.7,1.2s.5.2.5.5.7,1.2.7,1.2l1.2,1.2s-.5,1.6,0,2.1c.5.5.9.5,1.4.7.5.2,4.2.7,4.2.7,0,0,1.9.2,2.1.5.2.2,1.4.5,2.1,0,.7-.5,7-1.6,7-1.6,0,0-.7-.9,0-1.2.7-.2,1.9-.5,3-1.4,1.2-.9,3.7-1.2,3.7-1.2,0,0-.5-1.2.2-1.6.7-.5,2.6-2.1,2.8-2.1s2.1,1.4,2.1,1.4c0,0-.7.7.5.7s1.4-.5,1.9-2.1c.5-1.6,1.9-2.3,1.9-2.3l.5-.7s-.5-.5,0-1.6c.5-1.2.9-2.6.9-2.6v-3.5s-.5,0,.5-.5c.9-.5,2.1-2.3,1.4-3.1-.7-.7-1.6-1.9-1.9-2.3Z"/>
<polygon class="cls-1" points="49 672.3 49 672.3 49 672.3 49 672.3"/>
<path class="cls-1" d="M93.5,737.5c-2.3-1.2-4.2-2.3-4.2-2.3h-1.9l-1.2,3.5s1.4,0,1.2.9c-.2.9-1.6.5-1.2,1.6.5,1.2-.5.7,1.6,1.6,2.1.9,2.8,1.9,3.7.9.9-.9,1.9-2.6,1.9-2.6h0l.7-1.2s1.6-1.4-.7-2.6Z"/>
<path class="cls-1" d="M86.2,745l-2.8-1.9s-2.8,2.6-2.8,3.1-2.1,3.3-1.6,4,1.2,1.6,2.8,1.6,3.3-1.6,5.4-2.6c2.1-.9,2.8-2.3,3.7-1.9.9.5,2.1-.5,2.1-.5h0l-1.9-1.2-4.9-.7Z"/>
<path class="cls-1" d="M101.7,742h0l-3.5,1.2s-.9,1.2-.9,1.6.5,2.6.5,2.6c0,0-.5.5-.5,1.6v2.3s1.2,3.1,2.1,3.5c.9.5,2.6-.5,3.1-1.9.5-1.4-.9-5.6-.9-5.6,0,0-2.8-1.2-2.1-1.4.7-.2,2.8-.5,3.1-.5s.9-.7.9-.7l-.7-2.8h-.9Z"/>
<path class="cls-1" d="M115.1,720.4l-1.2-1.6-.5-1.6h0v-2.6l-2.1-3.3s-1.6-1.2-1.9-1.6c-.2-.5-3-2.3-3-2.1s-2.3,2.3-2.3,2.6-3.7,2.8-3.7,2.8c0,0,1.2,4,1.2,4.2s-1.9.2-.5,1.9c1.4,1.6,5.9,2.1,5.9,2.1,0,0,.2-1.4.5.5.2,1.9-.5,2.8-.5,3.1s1.2,1.2,2.6,1.2,3.3-.2,3.5,0c.2.2,3.5.5,3.5.5,0,0,.2,0,0-2.1-.2-2.1-1.4-3.8-1.4-3.8Z"/>
<path class="cls-1" d="M125.6,711.9c.5,0,1.4-1.9,1.4-1.9,0,0-.5-.7.9-.5,1.4.2.9,0,1.6.5.7.5,1.2.5,1.4.5s1.2-.7.5-1.4c-.7-.7-1.4-.9-1.9-1.6-.5-.7-.5-2.6-.5-2.6,0,0-.5-1.4-.7-1.4h-2.8l-.9.9h0c-.7,0-3.5-.7-3.5-.7l-.5,2.8-2.1,1.4s-.9.5-.5,1.2c.5.7.9,1.2.7,1.6-.2.5-1.6,0-1.4.7.2.7,1.6,1.9,1.6,1.9,0,0-.7,1.2-.7,1.6s.9,1.4,1.4,1.4,1.9-2.1,1.9-2.1c0,0,.5-.9.9-1.2.5-.2,2.1-1.2,2.1-1.2h.9Z"/>
<path class="cls-1" d="M141.5,712.4c0-.2-.9-.5-1.2-.7-.2-.2-1.6-.7-1.6-.7l-3-.9h-1.7s-.7,2.1-.7,2.3-.5,1.2,0,1.4c.5.2,2.1.7,2.1.7v1.2c0,.5-.7.7.5,1.2,1.2.5,4.2-.5,4.2-.5h2.6s.9-1.4.5-1.9c-.5-.5-1.6-1.9-1.6-2.1Z"/>
<path class="cls-1" d="M128.6,725.1s.2-.2.5,0c.2.2,1.4-.5,1.4-.5,0,0-.2-.5.5-.5s1.9.5,1.9.5c0,0,.7-.2.9-.7.2-.5-.2-.9,0-1.4.2-.5,0-1.6,0-1.6l-.7-1.6s-.9-.2-1.4-.2-1.2-.2-1.2-.2l-.5-.2-.9-.7h0s-2.3-.2-2.6.5c-.2.7-.7,1.6-.9,1.9-.2.2-1.9.9-1.9.9l1.4,1.4s.2.7,1.2,1.2c.9.5,2.3,1.4,2.3,1.4Z"/>
<path class="cls-1" d="M121.1,719.9c-.3-.3-.7-.5-1.2-.5h0c-.6,0-1.1.6-1.2,1.2,0,.4.2.8.5,1.2.1.2.3.5.7.7.5.3,1.4.6,1.6.2.2-.2,0-.5-.2-1.6,0-.7,0-.9-.2-1.2Z"/>
<path class="cls-1" d="M137.3,747.6c-.9-.5-1.9-.9-1.9-.9l-.5-1.2.9-1.2.2-2.3-4.2-.5-2.1.5h0s-1.2,2.1-1.4,2.6c-.2.5-2.1,1.9-2.1,2.1s0,.7-.2,1.4c-.2.7-.9,1.9,0,2.3.9.5,2.1.7,2.1.7,0,0,2.6-1.4,2.8-.7.2.7.2,1.2,1.2,1.9.9.7,2.1.9,2.3.9s.2-1.2.9-1.4c.7-.2,2.6-1.9,2.6-1.9,0,0,.2-1.9-.7-2.3Z"/>
<path class="cls-1" d="M267.1,679.6s.5-.5,0-1.6c-.5-1.2-.7-2.1-.7-2.1l-.7-1.2-.5-1.4s-.5-.5-.2-1.2c.2-.7,0-2.6,0-2.6l-.2-.7.7-.9h0c.2-.2.5-2.1.5-2.1l-3.3-2.3s-1.4.9-1.2,1.9c.2.9.9,2.1.9,2.6s.2,1.6,0,2.1c-.2.5-.9,1.2-.9,1.4s-.7,1.4-.9,1.9c-.2.5-2.8,1.9-2.8,1.9,0,0-1.9,1.9-1.9,2.1s-1.2.2-.7,1.6c.5,1.4.5,3.3.5,3.3,0,0-1.2.9-.5,1.6.7.7,1.4,1.6,2.8,2.1,1.4.5,4.2,1.6,4.2,1.6l3.1.5s2.3-.9,1.9-1.4c-.5-.5-1.6-1.4-1.6-1.4l-.5-1.2s0-1.2.5-1.9,1.6-2.6,1.6-2.6Z"/>
<polygon class="cls-1" points="279.4 600.4 279.4 586.1 272 579.9 262.7 571.1 261.3 561.5 254.5 559.9 251.7 557.3 244.7 562.9 243.5 566.9 235.1 563.3 234.4 553 235.1 535.7 240.9 533.6 245.9 532.1 247.1 523.8 256.9 510.3 248.3 507 239.9 515.4 232.7 509.4 238.7 493.8 231.6 486.6 229.2 475.8 217.2 472.2 206.4 484.2 205.2 493.8 195.7 486.6 186.1 495 186.1 502.2 176.5 510.6 159.8 515.4 157.4 523.8 140.6 521.4 127.4 517.8 124.5 512.7 119.4 508.3 115.3 512.5 110.2 512.4 96.4 517.8 89.3 530 84.4 535 85.6 538.3 88.3 540.1 87.2 541.1 82.3 539.9 76.8 551.3 72.3 553.6 67.3 564.8 76.6 566.5 73.6 571.4 75.3 574.4 80.6 583.9 85.4 585.9 85.4 589.7 80 596 78.4 589.4 69.9 588.5 68.8 590 71.3 593.4 75.4 595.9 74.3 598.4 69.7 600 68.7 603.3 63 605.4 61.9 611.5 67.8 615.5 70.4 613.1 73.2 605.9 74.3 613.6 75.3 626.5 69.7 634.5 69.7 639.2 68.1 643.4 67.8 644.3 80.4 650.4 92.7 661.4 89.3 661.9 86.5 659.8 85.6 664 79 654.2 69.4 650 67.8 656.3 70.6 661.9 74.3 663.3 77.1 670.4 72.7 669.9 66.1 661.5 62.9 648.1 58.5 642.1 51.9 654.2 55.1 668.3 59.6 674.6 61.9 673.9 65.8 676.6 67.1 680.5 69.9 680.2 72.7 677.9 79 686.6 77.7 698.2 82.8 699.7 79.7 706.7 76.6 709.9 77.4 712.1 82.5 712.8 82.7 721.2 93.1 719.9 97.3 713.3 97.1 709.8 112.1 701.6 110.7 695.9 117.2 694.3 120.8 688.2 120.2 673.4 122.8 671.1 124.7 675.1 124.5 684.5 122.4 694.3 125.7 699.9 130.4 702.1 134.8 704.2 136.9 700.4 143.5 702.3 145.8 697.6 149.1 698.3 148.3 688.6 152.1 687 152.9 684.2 151.7 682 155 673 162 671.1 167.4 666.2 174.9 662.4 179.6 664 183.8 662.6 187.1 655.8 192.4 654.7 194.6 657.2 202.1 657.2 202.7 663.8 200.6 669.2 196 669.8 194.8 663.8 189.6 664 187.5 671.6 189.1 675.6 185.9 681.9 181.9 681.2 183.1 676 181.4 674.3 176 692.2 189.2 694.5 191.7 701.4 195.5 706.3 203.9 704.4 207.4 697.8 213.3 694.1 214.2 689.9 209.3 687.1 210 684 218 684.7 221 686.6 224.2 683.9 222.7 674.6 209.2 661.4 209.5 657 212.8 653.7 210.5 650.9 215.4 646.2 211.9 643.7 213.5 642.5 216.5 642.5 222 642.5 227.6 640.4 228.1 635.9 230.9 638 231.2 641.7 231.8 647.4 236.2 650.7 238.4 657 234.6 663.8 232.6 672.3 234.9 676.9 238.7 676.9 243.5 679.8 244.5 675.1 243.3 671.1 245.9 662.9 249.9 659.4 255 664.1 261.6 661 262.3 655.8 265.1 651.9 267 645.5 264.6 642 261.1 642.5 257.9 642 248 646 246.3 642 239.9 634.6 244.2 626.8 246.3 631 251.6 632.8 262.5 623.5 278.8 628.6 285.4 617.2 285.4 607.6 279.4 600.4"/>
</g>
<g id="nakdong" data-label-shift="dx,dy">
<path class="cls-1" d="M405.4,649.3l-2.1-.2-1.6,1.2-.9,1.6-1.2.2-.9-.5v-1.2l.2-.9,1.4-.9.9-.7v-.9l.9-.9.7-1.2-.2-1.6-.2-1.4.5-3v-1.9l-1.6-1.6-.9-.9.2-1.9.7-2.8h0c0,0,1.6-.5,1.6-.5,0,0,1.2-.9,1.2-1.2s.2-.7.2-.7l-.2-.9-1.4-.9-1.9-.7-1.6.2-1.2.5-.9,1.2-.7,1.2-.2,1.2.5,1.9v.9l-.5.7-.7.2-1.2.2-.9.5-.5.5-.2,1.2-.2.5h-1.4l-1.4.5s-.9.2-1.2.2-.7.7-.7.7l-1.2.5h-1.4c-.2,0-.7.2-.7.2l-.5.5v.5l.2.7s.7.7.9.7,0,.7,0,.7l.2.9v.7l.2.9.5.5.7.5s.5.5.7.5.2.7.2.7c0,0,.5.7.5.9v2.1h-.2s-.7-.2-.9-.2-.5-.5-.5-.5l-.9-.7h-.9l-.9-.5-.5-.7-.7-1.2-.7-.5-.5-.2-.5.2-.7.7-.7.2h-.9l-1.4-.9-1.4-.9-1.4-.7h-1.2l-.5.2-.7.9-.5.7-1.2.7s-.2,0-.5.2c-.2.2-.7.5-.7.5l-.5.7-.7,1.2-.5,1.2-.9.9v3.3l1.2,1.4s.7,1.4.7,1.6.2,1.4.2,1.4l.7,1.2.7,1.6,1.6.2,1.4-.9.7-1.2.7-.7h2.6l1.4-1.2.9-1.6.7-.9.7-.5.5.5.5.7v2.6s.5.5.7.5,0,.7,0,.7l-.7,1.2-1.2,1.9-.5.7-.2.5-.2.5-.9.5-1.2.2-1.4.2-.7.5-.5.5-.2.7,1.4.5.5.5v.7l.5.5.7.2h1.2l2.1.2.7.5.2.7-.2.5-.7.5s-.9.7-1.2.7-.5.9-.5.9l-.2.7.2.9v.7l.9.2.9.7,1.2.2v.9l-.2.2-.9.5-1.4.5-.7.2-.5.7v.9l.5.7s.5.5.7.5,1.4.2,1.4.2c0,0,1.4.2,1.6.2s.9-.5.9-.5l1.2-.5,1.2-.7.5-.7v-.7l.2-1.2s.5-.9.5-1.2.9-.5.9-.5l1.4-.2.2-.7-.9-.9-.7-1.4.5-1.6v-1.2l.7-.7,1.6-.2,1.6-.2.7-.9.2-1.2v-2.1l.7-.7,1.9.2,1.6-.2.5,2.1,1.2,1.4,2.1.9,1.6-.7.2-1.2-.2-1.2-1.6-1.2-.7-1.4.2-1.2-1.2-.7-.9-.9.5-1.4h1.9c.2,0,.7-.5.7-.5l.7-.9v-1.6h.9c.2,0,.7-.5.7-.5l.2-1.4s.2-.5.7-.7c.5-.2,0-1.4,0-1.4v-1.4Z"/>
<path class="cls-1" d="M330.3,650.8v-1.2l-.9-.5h0s-4.2-1.2-4.2-.9-2.8,0-2.8,0l-1.4.9s-.2.7-.2.9.2,1.2.2,1.2l.5.2.9.7,2.8.5h1.9l.9-.7.9-.7.9-.2.5-.2Z"/>
<polygon class="cls-1" points="333.8 654.8 332.6 654.1 332.2 652.9 331.7 652.7 331 652.9 329.8 653.9 329.6 654.8 329.1 656 329.1 657.6 329.4 659 329.8 659.7 330.8 659.7 331.9 659.5 332.4 659 333.8 659 335 658.8 335.2 657.8 335.2 656.9 334.7 655.7 333.8 654.8"/>
<polygon class="cls-1" points="308 649.2 307.6 648.4 307.6 647.5 308 646.3 308.7 645.2 308.7 644.2 308.3 644 307.3 643.5 306.4 642.8 305.5 641.9 304.7 641.2 303.1 641.4 302.2 642.8 302.2 644.7 301.2 645.2 300.8 644.2 300.5 642.6 301 641.4 301.7 639.8 302.4 638.4 303.6 637.2 303.6 636.5 303.3 635.8 302.6 635.8 301 636 299.8 637 298.4 638.1 298.2 639.5 297.3 640 295.6 640.9 294.7 642.4 293.5 643.5 293.3 644.9 293.3 645.9 293.5 647 294.2 648.2 295.4 648.4 297 649.2 299.4 649.2 301.2 648.7 303.1 649.2 304.1 649.9 306.2 651 307.8 652 308.3 651.7 309.4 651.7 309.7 651 309.2 650.3 308 649.2"/>
<path class="cls-1" d="M305.2,653.4l-1.2-.5-.9-.7-.7-.2h-2.1l-1.6-.2-.9-.7h-1.6l-1.2,1.2-1.6,1.2-.9.7v.7l-1.4-.2-.5-1.2-.9-1.4-.2-.7-.5-.9-1.2-.5-1.2-.5-.5-.5s-.2-.5-.2-.7-.2-1.2-.2-1.2v-.5c0-.2-.7-.7-.7-.7l-.2-.7-.5-1.2-.2-1.4.7-.9.2-.9,1.6-.2.7-.5.5-.2.2-.7.2-1.2.5-.5.7-.9v-.7l-.2-1.2-.5-1.4.2-1.4-2.1-1.4h-3.1l-1.9.2-1.4,1.2-.9,1.4-.5,1.2-.5,1.4v.7l-1.6.5-.9.5-.7.9s.2.5,0,.7c-.2.2-1.2.9-1.2.9v1.4l-.5,1.2-.7.5-.9.5-.2.9v1.4l.2,1.2-.2,1.4.5,1.6.5,1.9,1.2,1.9h0v1.6l.2,1.4.7.9.9.7.2.7-.2.7-.9.9-.9.7-.5.7-.2.7,1.2.7.7.5,1.4.7v.7l.2,1.2.5.9.9.5.9-.2h1.9l1.2-.2.9-.5.9-.9v-1.4l-.5-.9-.2-1.4.2-.7.7-.5,1.9-.2,1.2-.9h2.3l.5.9v.7s-.5.7-.5.9-.2,1.2-.2,1.2l-.5,1.4v.5l-.2.9.2.5.2.5.2,1.2v1.2l.5.9,2.1.2,2.3.7,1.6-.2,1.2.2.9-.7s.5.5.7.5.5.9.5.9l1.6.7,1.6.2h.7l.2-1.2-.2-1.4-.7-.7-.2-.9v-1.2l.5-.5.9-.9.7-.2.5-.5v-2.3s-.5-2.1-.5-2.3v-.9l.2-1.2.5-1.4s-.2-.9,0-.9.7-.7.7-.7l.7-.5.2-.9-.2-.9-1.2-.5Z"/>
<polygon class="cls-1" points="533.7 451.9 529.9 457.3 516.8 465.7 509.4 457.5 516.6 447.7 513.8 417.3 513.1 408.8 521.3 392.4 522.9 355.1 523.6 353.3 531.1 333.9 526.2 315.4 526.2 282.1 519.8 263.8 504.8 272.7 495 281.2 482.8 272 481.1 259.2 471.4 254.5 473.3 244.6 468.7 245 455.3 248.2 454.9 259.5 454.7 274.8 451.4 277.9 438.9 269.7 434.5 277.6 421.1 273 410.9 275.3 403.3 283.3 392.8 288.8 386.2 300.5 389 306.9 377.6 314.7 353.5 310.3 345.4 307.2 340.7 310.3 334.5 310.7 330 319.5 334.6 326 329.8 328.1 325 323.7 307.5 329.5 314.2 336 312.9 341.5 306.5 336.4 294.4 343.5 303.6 349.6 308.1 357.5 304 362.7 314.1 371.6 316.5 383.6 314.1 388.4 326.1 392 323.3 402.6 325.2 414.4 315.5 413.8 315 423.6 307.5 439.1 298 443 298.5 447.6 301.2 454.4 296.8 462.5 286.2 472.3 276.7 473.4 269.4 480.7 267.1 483.1 264.5 485.8 256.9 505.2 256.9 510.3 247.1 523.8 245.9 532.1 240.9 533.6 235.1 535.7 234.4 553 235.1 563.3 243.5 566.9 244.7 562.9 251.7 557.3 254.5 559.9 261.3 561.5 262.7 571.1 272 579.9 279.4 586.1 279.4 600.4 285.4 607.6 285.4 617.2 278.8 628.6 282.6 628.4 288.8 622.6 289.3 620.3 291.9 621.5 292.3 624.3 296.3 623 298.7 624.3 301.6 622.6 298.9 615.9 301.2 614.9 303.4 615.5 304.7 611.2 309.9 609.7 307.1 614.2 306.9 625.2 305.5 632.3 314.8 637.2 317 641.5 318.3 642.1 322.4 640.3 325.1 641.7 326.8 641.4 329.4 634.9 335.5 635.5 338.5 640.5 342.8 638.4 346.2 633 347.4 636.3 348.6 639.8 343.1 642.3 344.4 645.9 349.4 647.4 351.4 645.9 353.5 647.7 353.3 655.5 347.7 654.1 346.2 655.7 351 659.7 353.7 666.6 356.3 667.8 358.7 665.9 359.9 659.7 358.5 655.6 361.6 654.2 365.6 646.5 363.1 643.5 360.7 647.8 358.5 646.8 361.3 632.3 361.1 630.7 361 629.5 368.3 628 371.4 624.3 365.9 619.1 357.2 620.8 355.4 623.6 351.9 624.1 354.8 618.5 361.5 616.3 367.3 614.4 366.1 610.6 379 609.8 381.7 611.6 383.5 619.4 389.9 616.8 383.5 596.4 387.2 591.5 388.9 592.4 387.7 599.2 390.1 604.4 394.7 605 398 608.8 399.5 604.8 401.5 606.5 400.5 612.2 406.2 614.7 414 613.3 414.9 615.6 420.1 616.2 424.6 617.3 428.8 614.1 432.5 616.6 438.5 610.1 436.6 622.3 438 626.7 441.3 625.8 442.5 620.6 445.7 622.8 453.5 612.9 457 616.4 461.6 616.6 461.7 613.8 459.8 610.5 463.1 606.6 469.9 608.2 480.9 594.4 480.7 591.3 485.5 581.8 491 579.8 494.6 576.1 498.5 573.7 497.1 571.8 499.6 567.6 499.9 561.3 498 558.2 504.1 554 504 546.1 505.5 548.8 506.9 554.5 511.6 551.2 514.6 544.4 514.4 537.4 515.8 532.7 513.9 528 514 525.4 524.7 493.4 527.1 486.2 527.8 477.8 535.1 466.5 538 455.6 533.7 451.9"/>
</g>
<g id="geum" data-label-shift="dx,dy">
<path class="cls-1" d="M298,443l9.5-3.9,7.5-15.6.5-9.8,9.7.6-1.9-11.8,2.8-10.6-12-3.6,2.4-4.8-2.4-12-10.1-8.9-12.6-.6-8.4-10.8-16.3-1.1-7.6-19.2,17.5-7.4,2.3-2.4v-1.1c-.1,0,.4-1.3.4-1.3l-1.6-1.1.5-9.5,1.4-12.2-3.8-13-13.2-9.6-4.9-10-7.1-1.9-11.7,17.8-8.2,1.7-3,5.2-4.6,6.5-19.7,1.1-13.8-5.9-3.6.6-3.6,5.4h-1.2l-4.8-6-6.4-3.9-5-.2-9.1-18.5-5.4,3-2.4-5.7-11.4-2.8-11.7-9.2h-3.9c0,.1-3.4,4.8-3.4,4.8l1,6.2,2.1,5.4-1.2.8-4.4-7.5-6.7-3.2-6.3-.3-5.2,3.3,5.9,3.4-3.6,2.2,5.3,4.4,2.5.8.4,3-6.5,6.1-8.7,11.2-4-1.9,6.4-13.5-1.6-1.9,1.1-7.9-2.1-3-4.7,14.5-3,1.8-3.8-5.7-4.4,2.7.5,8.4-1.5,1.7-3.3-.8-4.2,3.2-5.1,13.8.5,1.5,3.9-.2.2-6.5,5.6,2.5,2.8,5.3-7.4,2.4v3c0,0,5.9,1.2,5.9,1.2l4.3-1.5,2.1-3.3,4-.3-1.4,6,4.1,3.7-2.4,9.4,4.3,6.1,1.1,27,11.6,2.1-6.6-26.7,2.3-4.1-6.3-4.9,1.7-1.9,1-6.9,2-1.5,2.2,9,4.4,3.2,3.1-1.9,3.1-6.1,1.8,1.5-1.9,8.6,2,14.4,1.9,13.7-2.8,6.5,8.2,7.8v2.1l-6.9,2.9,2.9,21.8-1.6,1.9-5.9,6.7,1.3,2.7,3.3-2.7,8.2,2.8,6.2,7.7-1.2,2.8,5.1,13.1,5.6,3.1-19.1.5-1.8,3.3,6.4,3.1-.6,10.7,7.3,2.8,3-3.3,8.9,1.9,9.4-3.4.3,1.4.2.9-3.1,5.4-5.1-.9-9.4,3.5,6.8,6.4,4.3,1.4-.2,5.6-20.6-5.8-6.4,13.7-18.1,10.4-1.9,6.6,5.9,4.7h19.9s3.8,4.2,3.8,4.2l-.5,3.4,3,5.1,13.2,3.6,16.8,2.4,2.4-8.4,16.8-4.8,9.6-8.4v-7.2l9.6-8.4,9.6,7.2,1.2-9.6,10.8-12,12,3.6,2.4,10.8,7.2,7.2-6,15.6,7.2,6,8.4-8.4,8.6,3.3v-5.1s7.6-19.4,7.6-19.4l12.2-12.4,9.6-1.1,10.5-9.8,4.5-8.1-2.7-6.8-.6-4.6Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

+62
View File
@@ -0,0 +1,62 @@
<!--
~ Copyright 2026 Hancom Inc. All rights reserved.
~
~ https://www.hancom.com/
-->
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Hwp SDK Wasm Sample</title>
<script
type="text/javascript"
data-type="css/app"
data-framework="aori|lab"
src="js/loadVersion.js"
></script>
<script
type="text/javascript"
data-type="css/app"
data-framework="aori|lab"
src="js/loadHead.js"
></script>
<script data-main="js/main" data-bundle="dist/main_bundle" src="js/loadMain.js"></script>
<style>
#hcwoViewScroll::-webkit-scrollbar {
height: 10px;
width: 10px;
}
#hcwoViewScroll::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.35);
border-radius: 5px;
}
#hcwoViewScroll::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.08);
}
</style>
</head>
<body>
<div id="status" style="display: none">
<div class="sppiner-content">
<div class="spinner" id="spinner"></div>
<div class="emscripten" id="statustext"></div>
</div>
</div>
<div id="status_downloading">
<div class="status-content">
<div class="emscriptenDownloading">
<span id="statusDownloadingText">구성 중 ... 0% </span>
<span id="statusDownloadingPercent" class="emscriptenDownloadingPercent"></span>
</div>
<div class="emscriptenDownloadingInfo"></div>
<progress id="download_progress" value="0" max="100"></progress>
</div>
</div>
<iframe id="download_iframe" style="width: 0px; height: 0px; border: 0; display: none"></iframe>
<script type="text/javascript" src="hwpsdk/loadHwpsdk.js"></script>
</body>
</html>
+66
View File
@@ -0,0 +1,66 @@
<!doctype html>
<html lang="ko" style="height: 100%">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>웹한글 기안기 브리지</title>
<script
type="text/javascript"
src="http://hwp.onasys.co.kr/webhwpctrl/js/hwpctrlapp/utils/util.js"
></script>
<script
type="text/javascript"
src="http://hwp.onasys.co.kr/webhwpctrl/js/hwpctrlapp/hwpCtrlApp.js"
></script>
<script
type="text/javascript"
src="http://hwp.onasys.co.kr/webhwpctrl/js/webhwpctrl.js"
></script>
<style>
html,
body,
#hwpctrl {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
border: 0;
}
#hwpctrl_frame {
width: 100% !important;
height: 100% !important;
}
</style>
</head>
<body>
<div id="hwpctrl"></div>
<script type="text/javascript">
(function () {
var params = new URLSearchParams(window.location.search);
var baseUrl = params.get("baseurl") || "http://hwp.onasys.co.kr/webhwpctrl";
if (baseUrl.charAt(baseUrl.length - 1) !== "/") {
baseUrl += "/";
}
var HwpCtrl = BuildWebHwpCtrl(
"hwpctrl",
baseUrl,
function () {
try {
window.parent.postMessage({ type: "hangulgiangi-ready" }, "*");
} catch (e) {
console.warn(e);
}
},
2,
);
// 부모(HangulGiangi)가 contentWindow.HwpCtrl 로 접근
window.HwpCtrl = HwpCtrl;
})();
</script>
</body>
</html>
+7 -1
View File
@@ -5,7 +5,9 @@ import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { useMenuStore } from "@/shared/lib";
import { PageLayout } from "@/shared/ui/page-layout";
import { authService } from "@/entities/auth";
import type { MenuItem } from "@/entities/menu";
import { BaseLayout } from "@common/components";
@@ -18,16 +20,20 @@ export function BaseLayoutWrapper({ children }: BaseLayoutWrapperProps) {
const pathname = usePathname();
const queryClient = useQueryClient();
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const handleMenuClick = useCallback(
(item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
},
[router],
[router, setCurrentMenuInfo],
);
return (
<BaseLayout
queryClient={queryClient}
authService={authService}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
+14 -2
View File
@@ -1,11 +1,15 @@
"use client";
import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { queryClient } from "@/shared/lib";
import { StoreProvider } from "@/shared/providers";
import { AlertDialog } from "@/shared/ui/alert-dialog";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { AuthWrapper } from "@/entities/auth";
import { MenuWrapper } from "@/entities/menu";
interface ProvidersProps {
children: React.ReactNode;
@@ -15,9 +19,17 @@ export function Providers({ children }: ProvidersProps) {
return (
<QueryClientProvider client={queryClient}>
<StoreProvider>
<AuthWrapper>{children}</AuthWrapper>
<ConfirmProvider>
<AuthWrapper>
<MenuWrapper>{children}</MenuWrapper>
<AlertDialog />
<ConfirmDialog />
</AuthWrapper>
</ConfirmProvider>
</StoreProvider>
<ReactQueryDevtools initialIsOpen={false} />
<div style={{ fontSize: "1.4rem" }}>
<ReactQueryDevtools initialIsOpen={false} />
</div>
</QueryClientProvider>
);
}
+15 -6
View File
@@ -6,10 +6,19 @@
@tailwind components;
@tailwind utilities;
html,
body {
color: var(--foreground);
background: var(--background);
font-family: "Noto Sans KR", sans-serif;
font-size: 10px;
@layer base {
:root {
--secondary-background: rgba(0, 0, 0, 0.04);
}
[data-theme="black"] {
--secondary-background: rgba(255, 255, 255, 0.09);
}
html,
body {
color: var(--foreground);
background: var(--background);
font-family: "Noto Sans KR", sans-serif;
font-size: 10px;
}
}
@@ -0,0 +1,58 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { PageResponse } from "@/shared/model";
import {
ACNTG_SE_SAVE_MODE,
type AcntgSeMngPutRequestDto,
type AcntgSeMngResponseDto,
type AcntgSeMngSaveRequestDto,
type AcntgSeMngSearchDto,
type AcntgSeMngService,
type AcntgSeMngTreeDto,
} from "../model/acntg-interface";
const BASE = ENDPOINT.AS_SERVICE.ACNTG_SE;
async function findAll(params: AcntgSeMngSearchDto): Promise<PageResponse<AcntgSeMngResponseDto>> {
const response = await client.get<PageResponse<AcntgSeMngResponseDto>>(BASE, { params });
return response.data;
}
async function findTree(): Promise<AcntgSeMngTreeDto[]> {
const response = await client.get<AcntgSeMngTreeDto[]>(`${BASE}/list/tree`);
return response.data;
}
async function findById(acntgSeCd: string): Promise<AcntgSeMngResponseDto> {
const response = await client.get<AcntgSeMngResponseDto>(`${BASE}/${acntgSeCd}`);
return response.data;
}
async function saveMulti(body: AcntgSeMngSaveRequestDto[]): Promise<AcntgSeMngResponseDto[]> {
const response = await client.post<AcntgSeMngResponseDto[]>(BASE, body);
return response.data;
}
async function update(acntgSeCd: string, body: AcntgSeMngPutRequestDto): Promise<string> {
const response = await client.put<string>(`${BASE}/${acntgSeCd}`, {
...body,
acntgSeCd,
mode: ACNTG_SE_SAVE_MODE.UPDATE,
});
return response.data;
}
async function deleteAcntgSeCd(acntgSeCd: string): Promise<string> {
const response = await client.delete<string>(`${BASE}/${acntgSeCd}`);
return response.data;
}
export const acntgSeMngService = {
findAll,
findTree,
findById,
saveMulti,
update,
delete: deleteAcntgSeCd,
} satisfies AcntgSeMngService;
+73
View File
@@ -0,0 +1,73 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type {
AcntgSeMngPutRequestDto,
AcntgSeMngSaveRequestDto,
AcntgSeMngSearchDto,
} from "../model/acntg-interface";
import { acntgSeMngService } from "./acntg-service";
const acntgSeMngBaseKey = [ENDPOINT.AS_SERVICE.ACNTG_SE] as const;
export function useAcntgSeMngPage(params: AcntgSeMngSearchDto) {
return useQuery({
queryKey: [...acntgSeMngBaseKey, "findAll", params],
queryFn: () => acntgSeMngService.findAll(params),
});
}
export function useAcntgSeMngTree() {
return useQuery({
queryKey: [...acntgSeMngBaseKey, "findTree"],
queryFn: () => acntgSeMngService.findTree(),
staleTime: Infinity,
});
}
export function useAcntgSeMng(acntgSeCd: string | null | undefined) {
return useQuery({
queryKey: [...acntgSeMngBaseKey, "findById", acntgSeCd],
queryFn: () => acntgSeMngService.findById(acntgSeCd!),
enabled: Boolean(acntgSeCd),
});
}
export function useSaveMultiAcntgSeMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: AcntgSeMngSaveRequestDto[]) => acntgSeMngService.saveMulti(body),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: acntgSeMngBaseKey });
},
});
}
export function useUpdateAcntgSeMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ acntgSeCd, body }: { acntgSeCd: string; body: AcntgSeMngPutRequestDto }) =>
acntgSeMngService.update(acntgSeCd, body),
onSuccess: (_data, { acntgSeCd }) => {
queryClient.invalidateQueries({ queryKey: acntgSeMngBaseKey });
queryClient.invalidateQueries({
queryKey: [...acntgSeMngBaseKey, "findById", acntgSeCd],
});
},
});
}
export function useDeleteAcntgSeMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (acntgSeCd: string) => acntgSeMngService.delete(acntgSeCd),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: acntgSeMngBaseKey });
},
});
}
+19
View File
@@ -0,0 +1,19 @@
export { acntgSeMngService } from "./api/acntg-service";
export {
useAcntgSeMng,
useAcntgSeMngPage,
useAcntgSeMngTree,
useDeleteAcntgSeMng,
useSaveMultiAcntgSeMng,
useUpdateAcntgSeMng,
} from "./api/queries";
export type {
AcntgSeMngResponseDto,
AcntgSeMngSaveRequestDto,
AcntgSeMngSearchDto,
AcntgSeMngService,
AcntgSeMngTreeDto,
AcntgSeSaveMode,
BizCstTreeDto,
} from "./model/acntg-interface";
export { ACNTG_SE_SAVE_MODE } from "./model/acntg-interface";
@@ -0,0 +1,88 @@
import type { PageableParams, PageResponse } from "@/shared/model";
export const ACNTG_SE_SAVE_MODE = {
CREATE: "C",
UPDATE: "U",
DELETE: "D",
} as const;
export type AcntgSeSaveMode = (typeof ACNTG_SE_SAVE_MODE)[keyof typeof ACNTG_SE_SAVE_MODE];
/** 백엔드 AcntgSeMngResponseDto */
export interface AcntgSeMngResponseDto {
acntgSeCd: string;
acntgSeCdNm: string;
upAcntgSeCd?: string;
cdLvNo: number;
useYn: string;
expln?: string;
sortSeq?: number;
rgtr?: string;
rgtrNm?: string;
mdfr?: string;
regDt?: string;
mdfcnDt?: string;
}
/** 백엔드 AcntgSeMngSearchDto + Pageable */
export interface AcntgSeMngSearchDto extends PageableParams {
acntgSeCd?: string;
acntgSeCdNm?: string;
useYn?: string;
}
/** 백엔드 AcntgSeMngSaveRequestDto */
export interface AcntgSeMngSaveRequestDto {
acntgSeCd: string;
mode: AcntgSeSaveMode;
acntgSeCdNm: string;
upAcntgSeCd?: string;
cdLvNo: number;
useYn?: string;
expln?: string;
sortSeq?: number;
}
/** PUT /{acntgSeCd} 요청 (mode는 서비스에서 U로 설정) */
export interface AcntgSeMngPutRequestDto {
acntgSeCd: string;
acntgSeCdNm: string;
upAcntgSeCd?: string;
cdLvNo: number;
useYn?: string;
expln?: string;
}
/** 백엔드 AcntgSeMngTreeDto */
export interface AcntgSeMngTreeDto {
indAcntgSeCdNm: string;
acntgSeCd: string;
acntgSeCdNm: string;
upAcntgSeCd?: string;
useYn: string;
treeLevel: number;
treePath: string;
}
/** 백엔드 BizCstTreeDto (InstlSncnMngDetResponseDto 등에서 참조) */
export interface BizCstTreeDto {
indAcntgSeCdNm: string;
acntgSeCd: string;
acntgSeCdNm: string;
useYn: string;
treeLevel: number;
treePath: string;
bscPlanAmt?: number;
bgtRfltAmt?: number;
tstmDesignAmt?: number;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface AcntgSeMngService {
findAll(params: AcntgSeMngSearchDto): Promise<PageResponse<AcntgSeMngResponseDto>>;
findTree(): Promise<AcntgSeMngTreeDto[]>;
findById(acntgSeCd: string): Promise<AcntgSeMngResponseDto>;
saveMulti(body: AcntgSeMngSaveRequestDto[]): Promise<AcntgSeMngResponseDto[]>;
update(acntgSeCd: string, body: AcntgSeMngPutRequestDto): Promise<string>;
delete(acntgSeCd: string): Promise<string>;
}
@@ -0,0 +1,40 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
CmCdAdstDstrComboResponseDto,
CmCdAdstDstrService,
} from "../model/adstdstr-interface";
const BASE = ENDPOINT.AS_SERVICE.ADSTDSTR;
async function findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]> {
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/sido`);
return response.data;
}
async function findUserSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]> {
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/user/sido`);
return response.data;
}
async function findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/gugun`, {
params: { sido },
});
return response.data;
}
async function findUserGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/user/gugun`, {
params: { sido },
});
return response.data;
}
export const cmCdAdstDstrService = {
findSidoCombo,
findUserSidoCombo,
findGugunCombo,
findUserGugunCombo,
} satisfies CmCdAdstDstrService;
@@ -0,0 +1,41 @@
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { IUserInfo } from "@/shared/store";
import { cmCdAdstDstrService } from "./adstdstr-service";
const cmCdAdstDstrBaseKey = [ENDPOINT.AS_SERVICE.ADSTDSTR] as const;
export function useFindSidoCombo() {
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findSidoCombo"],
queryFn: () => cmCdAdstDstrService.findSidoCombo(),
});
}
export function useFindUserSidoCombo(userInfo?: IUserInfo | null) {
const adstDstrCdByUser = userInfo?.laeSeCd?.substring(2, 4);
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findUserSidoCombo", adstDstrCdByUser ?? "all"],
queryFn: () => cmCdAdstDstrService.findUserSidoCombo(),
select: (data) =>
adstDstrCdByUser ? data.filter((item) => item.adstDstrCd === adstDstrCdByUser) : data,
});
}
export function useFindGugunCombo(sido: string) {
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findGugunCombo", sido],
queryFn: () => cmCdAdstDstrService.findGugunCombo(sido),
enabled: Boolean(sido),
});
}
export function useFindUserGugunCombo(sido: string) {
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findUserGugunCombo", sido],
queryFn: () => cmCdAdstDstrService.findUserGugunCombo(sido),
enabled: Boolean(sido),
});
}
+11
View File
@@ -0,0 +1,11 @@
export { cmCdAdstDstrService } from "./api/adstdstr-service";
export {
useFindGugunCombo,
useFindSidoCombo,
useFindUserGugunCombo,
useFindUserSidoCombo,
} from "./api/queries";
export type {
CmCdAdstDstrComboResponseDto,
CmCdAdstDstrService,
} from "./model/adstdstr-interface";
@@ -0,0 +1,13 @@
/** 백엔드 CmCdAdstDstrComboResponseDto */
export interface CmCdAdstDstrComboResponseDto {
adstDstrCd: string;
adstDstrNm: string;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface CmCdAdstDstrService {
findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
findUserSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
findUserGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
}
@@ -0,0 +1,24 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
AiAutoPrcsLatestResponseDto,
AiAutoPrcsMngService,
} from "../model/ai-auto-prcs-mng-interface";
const BASE = ENDPOINT.AS_SERVICE.AI_AUTO_PRCS_MNG;
async function findLatestPerAutoSeByFnrsDocNo(
fnrsCnsltnDocNo: string,
atmtnSeComCd?: string,
): Promise<AiAutoPrcsLatestResponseDto[]> {
const response = await client.get<AiAutoPrcsLatestResponseDto[]>(
`${BASE}/fnrs/${fnrsCnsltnDocNo}/latest`,
{ params: atmtnSeComCd ? { atmtnSeComCd } : undefined },
);
return response.data;
}
export const aiAutoPrcsMngService = {
findLatestPerAutoSeByFnrsDocNo,
} satisfies AiAutoPrcsMngService;
@@ -0,0 +1,30 @@
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { aiAutoPrcsMngService } from "./ai-auto-prcs-mng-service";
export const aiAutoPrcsMngKeys = {
all: [ENDPOINT.AS_SERVICE.AI_AUTO_PRCS_MNG] as const,
latest: (fnrsCnsltnDocNo: string, atmtnSeComCd?: string) =>
[...aiAutoPrcsMngKeys.all, "latest", fnrsCnsltnDocNo, atmtnSeComCd ?? "all"] as const,
};
interface UseAiAutoPrcsLatestOptions {
enabled?: boolean;
atmtnSeComCd?: string;
}
export function useAiAutoPrcsLatest(
fnrsCnsltnDocNo: string | null | undefined,
options: UseAiAutoPrcsLatestOptions = {},
) {
const { enabled = true, atmtnSeComCd } = options;
return useQuery({
queryKey: aiAutoPrcsMngKeys.latest(fnrsCnsltnDocNo!, atmtnSeComCd),
queryFn: () =>
aiAutoPrcsMngService.findLatestPerAutoSeByFnrsDocNo(fnrsCnsltnDocNo!, atmtnSeComCd),
enabled: Boolean(fnrsCnsltnDocNo) && enabled,
});
}
@@ -0,0 +1,8 @@
export { aiAutoPrcsMngService } from "./api/ai-auto-prcs-mng-service";
export { aiAutoPrcsMngKeys, useAiAutoPrcsLatest } from "./api/queries";
export type {
AiAutoPrcsLatestResponseDto,
AiAutoPrcsMngService,
AiAutoSeComCd,
} from "./model/ai-auto-prcs-mng-interface";
export { AI_AUTO_SE } from "./model/ai-auto-prcs-mng-interface";
@@ -0,0 +1,44 @@
/** 자동화구분 공통코드 (AIAUTOSE) */
export const AI_AUTO_SE = {
FILE_VRFC: "FILE_VRFC",
DRAFT_GEN: "DRAFT_GEN",
COST_VRFC: "COST_VRFC",
} as const;
export type AiAutoSeComCd = (typeof AI_AUTO_SE)[keyof typeof AI_AUTO_SE];
/** 백엔드 AiAutoPrcsLatestResponseDto */
export interface AiAutoPrcsLatestResponseDto {
atmtnPrcsUuid?: string;
fnrsCnsltnDocNo?: string;
pwrkCnsltnDocNo?: string;
crtDmndUuid?: string;
atmtnSeComCd?: string;
atmtnSeComNm?: string;
bfrDocSttsComCd?: string;
bfrDocSttsComNm?: string;
aftrDocSttsComCd?: string;
aftrDocSttsComNm?: string;
atmtnPrcsSttsComCd?: string;
atmtnPrcsSttsComNm?: string;
dmndDt?: string;
dmndSysId?: string;
rplyDt?: string;
rplyRsltComCd?: string;
prcsErrCn?: string | null;
lastSttsInqDt?: string;
sttsInqRsltComCd?: string;
rtrsmCtpvNmtm?: number;
lastRtrsmDt?: string;
lastFrcSttsChgYn?: string;
isFailed?: boolean;
isRetryable?: boolean;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface AiAutoPrcsMngService {
findLatestPerAutoSeByFnrsDocNo(
fnrsCnsltnDocNo: string,
atmtnSeComCd?: string,
): Promise<AiAutoPrcsLatestResponseDto[]>;
}
@@ -0,0 +1,36 @@
import { AxiosResponse } from "axios";
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { Attach, AttachService } from "../model/attach-interface";
async function findByAtflCd(atflCd: string): Promise<Attach[]> {
if (!atflCd) return [];
const response = await client.get<Attach[]>(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflCd}`);
return response.data;
}
async function deleteAttachmentByAtflId(atflId: string): Promise<void> {
if (!atflId) return;
await client.delete(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`);
}
async function downloadAttachmentByAtflCd(atflCd: string): Promise<AxiosResponse> {
return await client.get<Blob>(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD}/${atflCd}`, {
responseType: "blob",
});
}
async function downloadAttachmentByAtflId(atflId: string): Promise<AxiosResponse> {
return await client.get<Blob>(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD_BY_ATFL_ID}/${atflId}`, {
responseType: "blob",
});
}
export const attachService = {
findByAtflCd,
deleteAttachmentByAtflId,
downloadAttachmentByAtflCd,
downloadAttachmentByAtflId,
} satisfies AttachService;
@@ -0,0 +1,54 @@
import { useMutation } from "@tanstack/react-query";
import type { AxiosResponse } from "axios";
import { ENDPOINT } from "@/shared/config";
import { parseFilenameFromResponse } from "../lib/parse-filename-from-response";
import { attachService } from "./attach-service";
const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const;
const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const;
const attachDownloadByAtflIdKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD_BY_ATFL_ID] as const;
function triggerBlobDownload(response: AxiosResponse) {
const filename = parseFilenameFromResponse(response);
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(link);
}
export function useDeleteAttachment() {
return useMutation({
mutationKey: [...attachBaseKey, "delete"],
mutationFn: (atflId: string) => attachService.deleteAttachmentByAtflId(atflId),
});
}
export function useDownloadAttachmentByAtflCd() {
return useMutation({
mutationKey: [...attachDownloadBaseKey, "download"],
mutationFn: async (atflCd: string) => {
const response = await attachService.downloadAttachmentByAtflCd(atflCd);
triggerBlobDownload(response);
},
});
}
export function useDownloadAttachmentByAtflId() {
return useMutation({
mutationKey: [...attachDownloadByAtflIdKey, "download"],
mutationFn: async (atflId: string) => {
const response = await attachService.downloadAttachmentByAtflId(atflId);
triggerBlobDownload(response);
},
});
}
+12
View File
@@ -0,0 +1,12 @@
export { attachService } from "./api/attach-service";
export {
useDeleteAttachment,
useDownloadAttachmentByAtflCd,
useDownloadAttachmentByAtflId,
} from "./api/queries";
export { parseFilenameFromResponse } from "./lib/parse-filename-from-response";
export type { Attach, AttachService } from "./model/attach-interface";
export {
AttachmentDownloadLink,
type AttachmentDownloadLinkProps,
} from "./ui/attachment-download-link";
@@ -0,0 +1,11 @@
import type { AxiosResponse } from "axios";
export function parseFilenameFromResponse(response: AxiosResponse): string {
const contentDisposition = response.headers["content-disposition"];
if (!contentDisposition) return "download";
const filenameMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/);
if (!filenameMatch?.[1]) return "download";
return decodeURIComponent(filenameMatch[1]);
}
@@ -0,0 +1,24 @@
import { AxiosResponse } from "axios";
/** 백엔드 AtflMngResponseDto */
export interface Attach {
atflCd: string;
atflSn: number;
atflId: string;
physFileNm: string;
orgnlFileNm: string;
atflFlieSz: number;
atflType: string;
dwnldNocs: number;
delYn: string;
lnkgDmnId: string;
lnkgDmnNm: string;
regDt: string;
}
export interface AttachService {
findByAtflCd: (atflCd: string) => Promise<Attach[]>;
deleteAttachmentByAtflId: (atflId: string) => Promise<void>;
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
downloadAttachmentByAtflId: (atflId: string) => Promise<AxiosResponse>;
}
@@ -0,0 +1,54 @@
"use client";
import { Paperclip } from "lucide-react";
import { cn } from "@common/components";
import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "../api/queries";
interface AttachmentDownloadLinkProps {
atflCd?: string;
atflId?: string;
fileName?: string;
className?: string;
}
function AttachmentDownloadLink({
atflCd,
atflId,
fileName,
className,
}: AttachmentDownloadLinkProps) {
const downloadByAtflCd = useDownloadAttachmentByAtflCd();
const downloadByAtflId = useDownloadAttachmentByAtflId();
const displayName = fileName || "첨부파일";
const canDownload = Boolean(atflId || atflCd);
const isPending = atflId ? downloadByAtflId.isPending : downloadByAtflCd.isPending;
function handleClick() {
if (atflId) {
downloadByAtflId.mutate(atflId);
return;
}
if (atflCd) downloadByAtflCd.mutate(atflCd);
}
return (
<button
type="button"
className={cn(
"inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline disabled:cursor-wait disabled:opacity-60",
className,
)}
aria-label={`${displayName} 다운로드`}
disabled={isPending || !canDownload}
onClick={handleClick}
>
<Paperclip className="my-1 size-5 shrink-0" />
{displayName}
</button>
);
}
export { AttachmentDownloadLink };
export type { AttachmentDownloadLinkProps };
@@ -0,0 +1,93 @@
import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
import {
type AuthPayload,
type AuthService,
clearDeviceId,
getOrCreateDeviceId,
} from "@common/auth";
import { clearRefreshTimer, setToken, tokenManager } from "../lib/token-manager";
async function login(payload: AuthPayload): Promise<void> {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Device-Id": getOrCreateDeviceId(),
},
body: JSON.stringify(payload),
});
const data = await response.json();
const accessToken = data[ACCESS_TOKEN];
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
throw new Error("인증 토큰을 받지 못했습니다.");
}
setToken(CLAIM_NAME, accessToken);
setToken(AUTH_USER_ID, tokenId);
// const remainingMs = getRemainingMs(accessToken);
// if (remainingMs > 0) {
// setupRefreshTimer(silentRefresh, remainingMs);
// }
}
async function silentRefresh(): Promise<void> {
console.log(`[silentRefresh] Called at ${new Date().toISOString()}`);
try {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, {
method: "PUT",
headers: {
"X-Device-Id": getOrCreateDeviceId(),
},
});
const data = await response.json();
if (!response.ok) {
throw new Error(data?.message ?? "토큰 갱신에 실패했습니다.");
}
const accessToken = data[ACCESS_TOKEN];
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
throw new Error("갱신된 인증 토큰을 받지 못했습니다.");
}
setToken(CLAIM_NAME, accessToken);
setToken(AUTH_USER_ID, tokenId);
// const remainingMs = getRemainingMs(accessToken);
// if (remainingMs > 0) {
// setupRefreshTimer(silentRefresh, remainingMs);
// }
} catch (error) {
console.error(`[silentRefresh] Failed at ${new Date().toISOString()}`, error);
tokenManager.clearAuthToken();
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
}
}
}
async function logout(): Promise<void> {
await fetch("/api/auth/logout", { method: "POST" });
tokenManager.clearAuthToken();
clearDeviceId();
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
}
}
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend: clearRefreshTimer,
logout,
};
@@ -1,92 +0,0 @@
import { AxiosError } from "axios";
import {
ACCESS_TOKEN,
AUTH_USER_ID,
CLAIM_NAME,
ENDPOINT,
JWT_EXPIRATION_TIME,
} from "@/shared/config";
import { tokenManager } from "../lib/token-manager";
import type { ILogin, LoginService } from "../model/login-interface";
export class LoginApiService implements LoginService {
private static instance: LoginApiService;
private constructor() {}
public static getInstance(): LoginApiService {
if (!LoginApiService.instance) {
LoginApiService.instance = new LoginApiService();
}
return LoginApiService.instance;
}
public async login(payload: ILogin): Promise<void> {
try {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
method: "POST",
body: JSON.stringify({
userId: payload.userId,
password: payload.password,
}),
});
const data = await response.json();
console.log("data ", data);
const accessToken = data[ACCESS_TOKEN];
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
if (data.message == "Invalid Credentials") {
throw new Error("아이디와 비밀번호가 일치하지 않습니다.");
} else if (data.message == "User not found") {
throw new Error("존재하지 않는 아이디입니다.");
} else if (data.message == "User is not active") {
throw new Error("활성화되지 않은 계정입니다.");
} else if (data.message == "User is not verified") {
throw new Error("이메일 인증이 필요합니다.");
} else {
throw new Error(data.message);
}
}
tokenManager.setToken(CLAIM_NAME, accessToken);
tokenManager.setToken(AUTH_USER_ID, tokenId);
tokenManager.setupRefreshTimer(() => this.silentRefresh(), JWT_EXPIRATION_TIME);
} catch (error) {
if (error instanceof AxiosError) {
throw new Error(`Login 실패 ${error.response?.data?.message}`);
}
throw new Error(`Login 실패 ${error}`);
}
}
public async silentRefresh(): Promise<void> {
try {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, {
method: "PUT",
});
const data = await response.json();
const accessToken = data[ACCESS_TOKEN];
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
throw new Error("Missing required authentication tokens");
}
tokenManager.setToken(CLAIM_NAME, accessToken);
tokenManager.setToken(AUTH_USER_ID, tokenId);
} catch (error) {
tokenManager.clearAuthToken();
if (error instanceof AxiosError) {
throw new Error(`Silent refresh failed: ${error.response?.data?.message}`);
}
throw new Error(`Silent refresh failed: ${error}`);
}
}
}
export const LoginApi = LoginApiService.getInstance();
+2 -1
View File
@@ -1,2 +1,3 @@
export { LoginApi } from "./api/login-service";
export { authService } from "./api/auth-service";
export { AuthWrapper } from "./lib/auth-wrapper";
export { tokenManager } from "./lib/token-manager";
+56 -27
View File
@@ -1,42 +1,71 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { isAxiosError } from "axios";
import { client } from "@/shared/api";
import { AUTH_LOGIN_REDIRECT_URL, AUTH_USER_ID, ENDPOINT } from "@/shared/config";
import { getCookie, useUserStore } from "@/shared/lib";
import { AUTH_LOGIN_REDIRECT_URL, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
import { useUserStore } from "@/shared/lib";
import type { IUserInfo } from "@/shared/store";
import { getRemainingMs } from "@common/auth";
import { isAtLoginRedirectUrl } from "@common/network";
import { authService } from "../api/auth-service";
import { getToken, setupRefreshTimer } from "./token-manager";
const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
const { userInfo, setUserInfo } = useUserStore((state) => state);
async function authCheck() {
if (isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) {
return;
}
const userId = getCookie(AUTH_USER_ID);
if (!userId) {
throw new Error("Missing required authentication tokens");
}
const response = await client.get<IUserInfo>(`${ENDPOINT.USER_SERVICE.USERS}/info/me`);
if (!userInfo) setUserInfo(response.data);
}
const router = useRouter();
const userInfo = useUserStore((state) => state.userInfo);
const setUserInfo = useUserStore((state) => state.setUserInfo);
useEffect(() => {
(async () => {
try {
await authCheck();
} catch (error) {
console.error(error);
setUserInfo(null);
if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) {
window.location.assign(AUTH_LOGIN_REDIRECT_URL);
}
const token = getToken(CLAIM_NAME);
if (token) {
const remainingMs = getRemainingMs(token);
if (remainingMs > 0) {
console.log(`[AuthWrapper] Refresh timer set: ${remainingMs}ms (${remainingMs / 1000}s)`);
setupRefreshTimer(authService.silentRefresh, remainingMs);
}
})();
}
if (isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) return;
async function authCheck() {
const userId = getToken(AUTH_USER_ID);
if (!userId) {
router.replace(AUTH_LOGIN_REDIRECT_URL);
return;
}
if (userInfo) return;
const response = await client.get<IUserInfo>(`${ENDPOINT.USER_SERVICE.USERS}/info/me`);
setUserInfo(response.data);
}
authCheck().catch((error) => {
if (isAxiosError(error) && error.response?.status === 401) return;
console.error(error);
setUserInfo(null);
if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) {
router.replace(AUTH_LOGIN_REDIRECT_URL);
}
});
}, []);
useEffect(() => {
function handleAuthLogout() {
setUserInfo(null);
if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) {
router.replace(AUTH_LOGIN_REDIRECT_URL);
}
}
window.addEventListener("auth:logout", handleAuthLogout);
return () => window.removeEventListener("auth:logout", handleAuthLogout);
}, []);
return <>{children}</>;
+13 -80
View File
@@ -1,82 +1,15 @@
import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config";
import { getCookie, removeCookie,setCookie } from "@/shared/lib";
import { authConfig } from "@/shared/config/auth";
import { createTokenManager } from "@common/auth";
export class TokenManager {
private static instance: TokenManager;
private refreshTimer: NodeJS.Timeout | null = null;
export const tokenManager = createTokenManager(authConfig);
private constructor() {}
public static getInstance(): TokenManager {
if (!TokenManager.instance) {
TokenManager.instance = new TokenManager();
}
return TokenManager.instance;
}
public getToken(key: string): string | undefined {
return getCookie(key);
}
public setToken(key: string, value: string): void {
const domain = this.getCookieDomain();
setCookie(key, value, {
secure: window.location.protocol === "https:",
sameSite: "lax",
domain: domain,
path: "/", // 모든 경로에서 접근 가능
});
}
private getCookieDomain(): string | undefined {
const hostname = window.location.hostname;
// 개발 환경에서는 localhost 도메인 사용
if (hostname === "localhost" || hostname === "127.0.0.1") {
return undefined; // localhost에서는 도메인 설정하지 않음
}
// 운영 환경에서는 서브도메인을 포함한 최상위 도메인 사용
if (hostname.includes("hasuon.or.kr")) {
return ".hasuon.or.kr";
}
// 기타 환경에서는 현재 호스트명 사용
return hostname;
}
public removeToken(key: string): void {
const domain = this.getCookieDomain();
removeCookie(key, { domain, path: "/" });
}
public setupRefreshTimer(
callback: () => Promise<void>,
expirationTime: number,
): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
this.refreshTimer = setTimeout(() => {
callback().catch((error) => {
console.error("Silent refresh failed:", error);
this.clearAuthToken();
});
}, expirationTime - 60000);
}
public clearAuthToken(): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
this.removeToken(CLAIM_NAME);
this.removeToken(AUTH_USER_ID);
}
}
export const tokenManager = TokenManager.getInstance();
export const {
getToken,
setToken,
removeToken,
setupRefreshTimer,
clearRefreshTimer,
getAccessToken,
getRefreshToken,
getUserId,
} = tokenManager;
@@ -1,9 +0,0 @@
export interface ILogin {
userId: string;
password: string;
}
export interface LoginService {
login(payload: ILogin): Promise<void>;
silentRefresh(): Promise<void>;
}
@@ -0,0 +1,59 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import { buildBnsSearchDto } from "../lib/build-bns-search-dto";
import type {
BnsDetailResponseWrapperDto,
BnsIfService,
BnsListResponseWrapperDto,
BnsSearchDto,
EmCodeListResponseDto,
FacilityListResponseWrapperDto,
FacilitySearchDto,
} from "../model/bns-if-interface";
const BASE = ENDPOINT.AS_SERVICE.BNS_IF;
async function getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto> {
const response = await client.get<BnsListResponseWrapperDto>(`${BASE}/bns`, {
params: buildBnsSearchDto(params),
});
return response.data;
}
async function getBnsDetail(
gbn: string,
yr: string,
seqNo: string,
): Promise<BnsDetailResponseWrapperDto> {
const response = await client.get<BnsDetailResponseWrapperDto>(`${BASE}/bns/detail`, {
params: { gbn, yr, seqNo },
});
return response.data;
}
async function getEmCodeList(
dpmCd?: string,
lclgvrnmntCd?: string,
): Promise<EmCodeListResponseDto> {
const response = await client.get<EmCodeListResponseDto>(`${BASE}/emCodeList`, {
params: { dpmCd, lclgvrnmntCd },
});
return response.data;
}
async function getFacilityList(
params: FacilitySearchDto,
): Promise<FacilityListResponseWrapperDto> {
const response = await client.get<FacilityListResponseWrapperDto>(`${BASE}/facilityList`, {
params,
});
return response.data;
}
export const bnsIfService = {
getBnsList,
getBnsDetail,
getEmCodeList,
getFacilityList,
} satisfies BnsIfService;
@@ -0,0 +1,42 @@
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type { BnsSearchDto, FacilitySearchDto } from "../model/bns-if-interface";
import { bnsIfService } from "./bns-if-service";
const bnsIfBaseKey = [ENDPOINT.AS_SERVICE.BNS_IF] as const;
export function useBnsList(params: BnsSearchDto, options?: { enabled?: boolean }) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getBnsList", params],
queryFn: () => bnsIfService.getBnsList(params),
enabled: options?.enabled,
placeholderData: keepPreviousData,
});
}
export function useBnsDetail(gbn: string, yr: string, seqNo: string) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getBnsDetail", gbn, yr, seqNo],
queryFn: () => bnsIfService.getBnsDetail(gbn, yr, seqNo),
enabled: Boolean(gbn && yr && seqNo),
});
}
export function useEmCodeList(dpmCd?: string, lclgvrnmntCd?: string) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getEmCodeList", dpmCd, lclgvrnmntCd],
queryFn: () => bnsIfService.getEmCodeList(dpmCd, lclgvrnmntCd),
});
}
export function useFacilityList(params: FacilitySearchDto, options?: { enabled?: boolean }) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getFacilityList", params],
queryFn: () => bnsIfService.getFacilityList(params),
enabled: options?.enabled,
placeholderData: keepPreviousData,
});
}
+16
View File
@@ -0,0 +1,16 @@
export { bnsIfService } from "./api/bns-if-service";
export { useBnsDetail, useBnsList, useEmCodeList, useFacilityList } from "./api/queries";
export { buildBnsSearchDto } from "./lib/build-bns-search-dto";
export type {
BnsDetailResponseDto,
BnsDetailResponseWrapperDto,
BnsIfService,
BnsListResponseDto,
BnsListResponseWrapperDto,
BnsSearchDto,
EmCodeItemDto,
EmCodeListResponseDto,
FacilityListResponseWrapperDto,
FacilityResponseDto,
FacilitySearchDto,
} from "./model/bns-if-interface";
@@ -0,0 +1,20 @@
import type { BnsSearchDto } from "../model/bns-if-interface";
export function buildBnsSearchDto(input: {
cPage: number;
searchStartYear?: string;
searchSido?: string;
searchGugun?: string;
}): BnsSearchDto {
const dto: BnsSearchDto = { cPage: input.cPage };
const year = input.searchStartYear?.trim();
const sido = input.searchSido?.trim();
const gugun = input.searchGugun?.trim();
if (year) dto.searchStartYear = year;
if (sido) dto.searchSido = sido;
if (gugun) dto.searchGugun = gugun;
return dto;
}
@@ -0,0 +1,205 @@
/** 백엔드 BnsSearchDto */
export interface BnsSearchDto {
searchStartYear?: string;
cPage: number;
searchSido?: string;
searchGugun?: string;
}
/** 백엔드 BnsListResponseWrapperDto */
export interface BnsListResponseWrapperDto {
list: BnsListResponseDto[];
cnt: number;
}
/** 백엔드 BnsListResponseDto */
export interface BnsListResponseDto {
cPage: number;
gbn: number;
chk: number;
seqNo: string;
rgnNm: string;
sido: string;
gugun: string;
bzCd: string;
bzNm: string;
yr: string;
strtYr: string;
edYr: string;
totBzamt: string;
}
/** 백엔드 BnsDetailResponseWrapperDto */
export interface BnsDetailResponseWrapperDto {
saup: unknown;
detail: BnsDetailResponseDto;
}
/** 백엔드 BnsDetailResponseDto */
export interface BnsDetailResponseDto {
cPage: number;
seqNo: string;
confBzCd: string;
chngDgr: string;
yr: string;
sido: string;
gugun: string;
rvbsnNm: string;
lgbsnNm: string;
mdmbsnNm: string;
lclgvrnmntNm: string;
accKdNm: string;
bzKdNm: string;
untBzNm: string;
dtlBzNm: string;
mevBzNm: string;
ddtlBzNm: string;
strtYr: string;
edYr: string;
aidRt: string;
bzStatus: string;
bzCd: string;
bzNm: string;
bzLoc: string;
efflWqltStndrdNm: string;
goalYear: string;
bssProm: string;
uppPlan: string;
techInsp: string;
bssCont: string;
rmk: string;
inflBscFctNm: string;
inflBscFctCd: string;
trtMtd: string;
swrExtns: string;
swrChng: string;
swrRnvt: string;
totFamNum: string;
pumpCnt: string;
rvWbsn: string;
rvText: string;
imprvExpctFlw: string;
imprvExpctWqlt: string;
trtplntPlEfflWqlt: string;
wqltAvg: string;
wqltHgh: string;
wqltLow: string;
stdCost: string;
bzEtc: string;
bscFctCd: string;
bscFctNm: string;
fctCpc: string;
fctCpcAfr: string;
trtDstrArea: string;
trtDstrAreaAfr: string;
swrTrtPopl: string;
swrTrtPoplAfr: string;
adstPopl: string;
}
/** 백엔드 EmCodeItemDto */
export interface EmCodeItemDto {
code: string;
data: string;
upCode: string;
}
/** 백엔드 EmCodeListResponseDto */
export interface EmCodeListResponseDto {
rvbsnList: EmCodeItemDto[];
lgbsnList: EmCodeItemDto[];
mdmbsnList: EmCodeItemDto[];
accList: EmCodeItemDto[];
bzList: EmCodeItemDto[];
untList: EmCodeItemDto[];
dtlList: EmCodeItemDto[];
mevList: EmCodeItemDto[];
ddtlList: EmCodeItemDto[];
}
/** 백엔드 FacilitySearchDto */
export interface FacilitySearchDto {
sido?: string;
gugun?: string;
}
/** 백엔드 FacilityResponseDto */
export interface FacilityResponseDto {
bscFctCd?: string;
bscFctNm?: string;
addr?: string;
fctCpc?: string;
fctCpc1?: string;
fctCpc2?: string;
fctCpc3?: string;
totInflAmntAve?: unknown;
pInflAmntAve?: unknown;
oInflAmntAve?: unknown;
aInflAmntAve?: unknown;
totTputAve?: unknown;
pTputAve?: unknown;
oTputAve?: unknown;
aTputAve?: unknown;
inflAssctBfrWqltBod?: unknown;
efflWqltBod?: unknown;
inflAssctBfrWqltCod?: unknown;
efflWqltCod?: unknown;
inflAssctBfrWqltSs?: unknown;
efflWqltSs?: unknown;
inflAssctBfrWqltTn?: unknown;
efflWqltTn?: unknown;
inflAssctBfrWqltTp?: unknown;
efflWqltTp?: unknown;
inflAssctBfrWqltClfgrpcnt?: unknown;
efflWqltClfgrpcnt?: unknown;
tmEfficiency?: unknown;
pcLoad?: unknown;
trtFrfHanNm?: string;
applctNtcheNm?: string;
excretionsTot?: unknown;
stkrsngWstwtrTot?: unknown;
lchtTot?: unknown;
etcTot?: unknown;
tel?: string;
rnDt?: string;
compDt?: string;
tot?: unknown;
oprClsNm?: string;
trstOprEtpNm?: string;
yrstpTrstCst?: string;
trstOprDt?: string;
trstAfr?: string;
dsfctNm?: string;
rvNm?: string;
mnstr?: string;
wbsnNm?: string;
efflWqltStndrdNm?: string;
elcpwAmnt?: unknown;
toeRt?: unknown;
toeSum?: unknown;
co2Emissions?: unknown;
toeQnty?: unknown;
toeRt2?: unknown;
useQnty?: unknown;
eqpPrmsDt?: string;
mdmbsn?: string;
lgbsn?: string;
lgbsnBod?: string;
cntrTrtFctNm?: string;
totWtrQrm?: unknown;
inflAssctBfrWqltToc?: unknown;
efflWqltToc?: unknown;
}
/** 백엔드 FacilityListResponseWrapperDto */
export interface FacilityListResponseWrapperDto {
fctList: FacilityResponseDto[];
}
/** Controller public 메서드명·시그니처와 동일 */
export interface BnsIfService {
getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto>;
getBnsDetail(gbn: string, yr: string, seqNo: string): Promise<BnsDetailResponseWrapperDto>;
getEmCodeList(dpmCd?: string, lclgvrnmntCd?: string): Promise<EmCodeListResponseDto>;
getFacilityList(params: FacilitySearchDto): Promise<FacilityListResponseWrapperDto>;
}
@@ -0,0 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type { UserPostSearchParams } from "../model/post-interface";
import { postApi } from "./post-service";
export function useUserPostList(params?: UserPostSearchParams) {
return useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, params],
queryFn: () => postApi.getUserPostList(params),
refetchOnWindowFocus: false,
});
}
@@ -0,0 +1,16 @@
import { client } from "@/shared/api";
import { AS_DASHBOARD_BBS_ID, ENDPOINT } from "@/shared/config";
import { PostService, UserPostListResponse, UserPostSearchParams } from "../model/post-interface";
async function getUserPostList(params?: UserPostSearchParams): Promise<UserPostListResponse> {
const response = await client.get<UserPostListResponse>(
`${ENDPOINT.BOARD_SERVICE.POSTS}/user/${AS_DASHBOARD_BBS_ID}`,
{ params },
);
return response.data;
}
export const postApi: PostService = {
getUserPostList,
};
+8
View File
@@ -0,0 +1,8 @@
export { useUserPostList } from "./api/post-queries";
export { postApi } from "./api/post-service";
export type {
PostService,
UserPost,
UserPostListResponse,
UserPostSearchParams,
} from "./model/post-interface";

Some files were not shown because too many files have changed in this diff Show More