From 5d487fbee706124ba9ebcc487284e69145d91654 Mon Sep 17 00:00:00 2001 From: yu_no Date: Wed, 20 May 2026 17:15:32 +0900 Subject: [PATCH 1/2] Organize UIUX cases and add boundary adjustment review --- README.md | 116 ++------ README_KOR.md | 138 +++++---- cases/README.md | 15 + cases/boundary-adjustment/README.md | 142 +++++++++ .../assets/01-boundary-overview.png | Bin 0 -> 9162 bytes .../assets/02-adjustment-legend.png | Bin 0 -> 9085 bytes .../assets/03-linked-parcels.png | Bin 0 -> 8597 bytes .../assets/04-edit-mode.png | Bin 0 -> 7971 bytes cases/boundary-adjustment/assets/README.md | 17 ++ .../source-sanitized/README.md | 16 ++ .../java/BoundaryAdjustmentController.java | 118 ++++++++ .../java/BoundaryAdjustmentRepository.java | 41 +++ .../java/BoundaryAdjustmentService.java | 119 ++++++++ .../mapper/BoundaryAdjustmentMap.xml | 115 ++++++++ .../web/boundary-adjustment.jsp | 270 ++++++++++++++++++ cases/consult-management/README.md | 54 ++++ .../consult-management/assets}/after_1.png | Bin .../consult-management/assets}/after_2.png | Bin .../consult-management/assets}/after_3.png | Bin .../consult-management/assets}/before_1.png | Bin .../consult-management/assets}/before_2.png | Bin .../consult-management/assets}/before_3.png | Bin .../source-sanitized/README.md | 5 + .../source-sanitized/pom.xml | 0 .../java/gov/example}/biz/dao/ConsultDao.java | 0 .../example}/biz/service/ConsultService.java | 0 .../biz/service/impl/ConsultServiceImpl.java | 0 .../example}/biz/web/ConsultController.java | 0 .../gov/example}/sqlmap/ConsultMap.xml | 0 .../WEB-INF/jsp/map/mapSelectionPopup.jsp | 0 .../src}/main/webapp/css/consult.css | 0 .../src}/main/webapp/js/calendar/consult.js | 0 docs/README.md | 15 + docs/review-framework.md | 39 +++ docs/security-redaction.md | 35 +++ 35 files changed, 1097 insertions(+), 158 deletions(-) create mode 100644 cases/README.md create mode 100644 cases/boundary-adjustment/README.md create mode 100644 cases/boundary-adjustment/assets/01-boundary-overview.png create mode 100644 cases/boundary-adjustment/assets/02-adjustment-legend.png create mode 100644 cases/boundary-adjustment/assets/03-linked-parcels.png create mode 100644 cases/boundary-adjustment/assets/04-edit-mode.png create mode 100644 cases/boundary-adjustment/assets/README.md create mode 100644 cases/boundary-adjustment/source-sanitized/README.md create mode 100644 cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentController.java create mode 100644 cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentRepository.java create mode 100644 cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentService.java create mode 100644 cases/boundary-adjustment/source-sanitized/mapper/BoundaryAdjustmentMap.xml create mode 100644 cases/boundary-adjustment/source-sanitized/web/boundary-adjustment.jsp create mode 100644 cases/consult-management/README.md rename {images => cases/consult-management/assets}/after_1.png (100%) rename {images => cases/consult-management/assets}/after_2.png (100%) rename {images => cases/consult-management/assets}/after_3.png (100%) rename {images => cases/consult-management/assets}/before_1.png (100%) rename {images => cases/consult-management/assets}/before_2.png (100%) rename {images => cases/consult-management/assets}/before_3.png (100%) create mode 100644 cases/consult-management/source-sanitized/README.md rename pom.xml => cases/consult-management/source-sanitized/pom.xml (100%) rename {src/main/java/pplis => cases/consult-management/source-sanitized/src/main/java/gov/example}/biz/dao/ConsultDao.java (100%) rename {src/main/java/pplis => cases/consult-management/source-sanitized/src/main/java/gov/example}/biz/service/ConsultService.java (100%) rename {src/main/java/pplis => cases/consult-management/source-sanitized/src/main/java/gov/example}/biz/service/impl/ConsultServiceImpl.java (100%) rename {src/main/java/pplis => cases/consult-management/source-sanitized/src/main/java/gov/example}/biz/web/ConsultController.java (100%) rename {src/main/resources/pplis => cases/consult-management/source-sanitized/src/main/resources/gov/example}/sqlmap/ConsultMap.xml (100%) rename src/main/webapp/WEB-INF/jsp/gisMap/viewLxMapPop.jsp => cases/consult-management/source-sanitized/src/main/webapp/WEB-INF/jsp/map/mapSelectionPopup.jsp (100%) rename {src => cases/consult-management/source-sanitized/src}/main/webapp/css/consult.css (100%) rename {src => cases/consult-management/source-sanitized/src}/main/webapp/js/calendar/consult.js (100%) create mode 100644 docs/README.md create mode 100644 docs/review-framework.md create mode 100644 docs/security-redaction.md diff --git a/README.md b/README.md index e8e6305..5e19cd3 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,46 @@ -# Government Admin UI/UX Modernization Example +# Government Admin UI/UX Modernization Examples [한국어 README](README_KOR.md) -[![Java](https://img.shields.io/badge/Java-ED8B00?style=flat-square)](https://www.java.com/) -[![Maven](https://img.shields.io/badge/Maven-C71A36?style=flat-square&logo=apachemaven&logoColor=white)](https://maven.apache.org/) -[![OpenLayers](https://img.shields.io/badge/OpenLayers-1F6B75?style=flat-square&logo=openlayers&logoColor=white)](https://openlayers.org/) -[![UI/UX](https://img.shields.io/badge/UI%2FUX-modernization-2563EB?style=flat-square)](https://github.com/yungi0816/gov-admin-uiux-example) +This repository documents UI/UX improvement work for legacy public-sector administration systems. It does not expose the original production system. Each case keeps the workflow, design decisions, and sanitized implementation structure while removing internal table names, API names, organization identifiers, and real operational data. -This repository is a practical before/after example for modernizing a 10+ year-old public-sector admin system. +The goal is not a full visual redesign. These examples show how to improve task accuracy and reviewability inside strict constraints: JSP/Java screens, shared legacy CSS, audit logging, authorization rules, existing map components, and fixed business procedures. -The goal is not to redesign everything at once. The focus is to reduce user resistance, preserve familiar work patterns, and improve consultation management and map-based selection flows for real business users. +## Cases -## Summary +| Case | Topic | Improvement Focus | Document | +| --- | --- | --- | --- | +| 01 | Consultation management and map selection | Reorganized consultation flow and strengthened map-based selection | [cases/consult-management](cases/consult-management/README.md) | +| 02 | Boundary adjustment file and parcel review | Registers files with linked parcels and visualizes adjustment counts on the map | [cases/boundary-adjustment](cases/boundary-adjustment/README.md) | -- Target: legacy admin consultation and map-based workflow screens -- Main users: field operators and public-sector staff, especially users in their 40s to 60s -- Goal: reduce friction, improve task efficiency, and lower input errors -- Core methods: conversational consultation UI, OpenLayers map selection, report automation +## Documentation -## Who This Is For +| Area | Document | +| --- | --- | +| Documentation index | [docs/README.md](docs/README.md) | +| Review framework | [docs/review-framework.md](docs/review-framework.md) | +| Security redaction policy | [docs/security-redaction.md](docs/security-redaction.md) | -- Developers modernizing old Java/JSP admin systems -- Teams looking for UI/UX examples that respect existing user habits -- Portfolio builders who want to explain a practical before/after redesign +## Repository Structure -## Features - -- Conversational consultation management UI -- Map-based parcel selection to reduce manual input errors -- Automated operation report flow -- Performance notes for PL/SQL and query optimization examples - -## Quick Start - -1. Install Java 11+ and Maven. -2. Build from the project root. - -```bash -mvn clean package +```text +. +|-- README.md +|-- README_KOR.md +|-- docs/ +`-- cases/ + |-- consult-management/ + `-- boundary-adjustment/ ``` -3. Check sample DB/config files under `resources/` when available. - -## Before & After - -The gallery below shows how the interface changes from dense legacy screens to more guided task flows. +## Redaction Standard - - - - - - - - - -
BeforeAfter
- Before 1
- Dense text and table-based screen -

- Before 2
- Nested forms and long lists -

- Before 3
- Map flow not integrated -
- After 1
- Conversational consultation with key summary -

- After 2
- Simplified filters and shortcut actions -

- After 3
- Direct map-based selection -
- -## Design Principles - -- Keep base text around 16px to 18px for readability. -- Use 44px to 48px minimum control heights to reduce click mistakes. -- Use explicit labels and step-by-step progressive disclosure. -- Preserve enough contrast between text and backgrounds. -- Explain both the cause and next action in error messages. - -## Suggested Design Tokens - -```css -:root { - --font-size-base: 18px; - --line-height: 1.4; - --button-min-height: 48px; - --space-section: 24px; - --accent-color: #2b7cff; -} -``` +Production table names, API paths, work codes, schema names, package names, organization names, user identifiers, parcel identifiers, and internal map utilities are replaced with role-based sample names. The sanitized source files are intended to explain architecture and review decisions, not to run against a real environment. -## Contributing +## Image Workflow -- Report bugs or setup problems in [Issues](https://github.com/yungi0816/gov-admin-uiux-example/issues). -- Share public-sector or legacy UI improvement ideas in [Discussions](https://github.com/yungi0816/gov-admin-uiux-example/discussions). -- Documentation, accessibility, and screenshot explanation improvements are welcome as pull requests. +Each case owns its screenshots under `cases//assets/`. Add images using the filenames documented in each case README and the gallery will render directly from the README. ## License -No explicit license is currently provided. If this project is intended for open reuse, adding MIT or Apache-2.0 is recommended. +No explicit license is currently provided. Treat the repository as a portfolio-style documentation example unless a license is added. diff --git a/README_KOR.md b/README_KOR.md index 8b84199..78be320 100644 --- a/README_KOR.md +++ b/README_KOR.md @@ -1,83 +1,81 @@ -# 국가 행정 시스템 UI/UX 개선 예시 +# 국가 행정 시스템 UI/UX 개선 사례 [English README](README.md) -10년 이상 운영된 레거시 행정 시스템의 화면을 실사용자 중심으로 재설계한 사례입니다. - -핵심 목표는 기존 업무 흐름을 갑자기 뒤집지 않으면서, 상담 관리와 지도 기반 선택 작업의 사용성을 높이는 것입니다. +운영 중인 공공 행정 시스템에서 실제 제약을 전제로 진행한 UI/UX 개선 사례를 정리한 저장소입니다. 이 저장소는 원본 업무 시스템을 공개하지 않고, 화면 구조, 사용자 흐름, 설계 판단, 익명화된 코드 흐름만 남깁니다. + +핵심 관점은 전면 재설계가 아닙니다. 기존 JSP/Java 기반 화면, 공통 CSS, 권한/감사 로그, 지도 컴포넌트, 업무 절차를 유지해야 하는 조건에서 사용자가 더 정확하게 확인하고 덜 실수하도록 개선한 기록입니다. + +## 사례 목록 + +| Case | 주제 | 핵심 개선 | 자료 | +| --- | --- | --- | --- | +| 01 | 상담관리 및 지도 선택 흐름 | 상담 화면을 업무 맥락 중심으로 재정리하고 지도 기반 선택 흐름을 보강 | [cases/consult-management](cases/consult-management/README.md) | +| 02 | 경계조정 파일-필지 연계 검증 | 파일과 필지를 함께 등록하고 조정 횟수를 도면 위에서 확인 | [cases/boundary-adjustment](cases/boundary-adjustment/README.md) | + +## 공개 기준 + +국가/공공 시스템 성격상 아래 항목은 공개 저장소에 남기지 않습니다. + +| 구분 | 공개 저장소 처리 방식 | +| --- | --- | +| 실제 테이블, 뷰, 스키마명 | `EXAMPLE_*` 또는 역할 중심 명칭으로 치환 | +| 실제 API 경로, 업무 코드 | `/example/...` 형태의 샘플 경로로 치환 | +| 실제 패키지명, 시스템 약어 | `gov.example` 계열 샘플 패키지로 치환 | +| 기관명, 사용자명, 필지 고유번호 | 샘플 데이터 또는 설명 문장으로 대체 | +| 내부 지도/공통 유틸 구현 | 호출 목적만 남기고 구현 세부는 제거 | + +자세한 기준은 [docs/security-redaction.md](docs/security-redaction.md)를 기준으로 관리합니다. + +## 저장소 구조 + +```text +. +|-- README.md +|-- README_KOR.md +|-- docs/ +| |-- README.md +| |-- review-framework.md +| `-- security-redaction.md +`-- cases/ + |-- consult-management/ + | |-- README.md + | |-- assets/ + | `-- source-sanitized/ + `-- boundary-adjustment/ + |-- README.md + |-- assets/ + `-- source-sanitized/ +``` -## 핵심 요약 +## 리뷰 방식 -- 대상: 레거시 행정 시스템의 상담관리 및 지도 기반 기능 -- 주요 사용자: 40대 ~ 60대 현업 담당자 -- 목적: 사용성 저항 최소화, 업무 효율화, 입력 오류 감소 -- 핵심 기법: 대화형 상담 UI, OpenLayers 지도 선택, 운영 리포트 자동화 +각 사례는 단순 화면 캡처가 아니라 아래 순서로 정리합니다. -## 이런 분에게 유용합니다 +1. 업무 문제와 사용자 리스크 +2. 변경이 금지되거나 제한된 시스템 조건 +3. 그 제약 안에서 선택한 UI/UX 판단 +4. 프론트엔드, 서버, 데이터 흐름의 역할 분리 +5. 공개 가능한 코드 구조와 비공개 처리 기준 +6. 앞으로 제약이 풀릴 때 개선할 수 있는 후속 과제 -- 오래된 JSP/Java 기반 행정 시스템을 점진적으로 개선해야 하는 분 -- 현업 사용자의 거부감을 낮추는 UI/UX 설계 사례가 필요한 분 -- Before/After 스크린샷과 함께 포트폴리오형 개선 사례를 정리하고 싶은 분 +이 기준은 [docs/review-framework.md](docs/review-framework.md)에 정리되어 있습니다. -## 주요 기능 +## 이미지 추가 규칙 -- 대화형 상담관리 UI: 간단한 문장 입력으로 업무 흐름 유도 -- 지도 기반 필지 선택: 시각적 선택으로 오류 감소 -- 운영 리포트 자동 생성: 반복 업무 자동화로 시간 절감 -- 성능 가이드: PL/SQL 튜닝 및 쿼리 최적화 예시 +각 사례의 `assets/` 폴더에 지정된 파일명으로 이미지를 추가하면 README의 갤러리에서 바로 확인할 수 있습니다. -## 빠른 시작 +경계조정 사례는 다음 파일명을 사용합니다. -1. Java 11+ 및 Maven을 설치합니다. -2. 프로젝트 루트에서 빌드합니다. +| 파일명 | 용도 | +| --- | --- | +| `01-boundary-overview.png` | 파일 등록, 도면, 선택 필지 목록이 함께 보이는 전체 화면 | +| `02-adjustment-legend.png` | 경계조정 횟수 색상 범례와 투명도 조정 | +| `03-linked-parcels.png` | 등록 파일 선택 후 연결 필지가 도면과 목록에 표시되는 상태 | +| `04-edit-mode.png` | 조회 상태에서 수정 모드로 전환해 필지 목록을 갱신하는 상태 | -```bash -mvn clean package -``` +## 현재 상태 -3. 샘플 DB/설정은 `resources/`의 샘플 파일을 확인하세요. - -## Before & After - - - - - - - - - - -
BeforeAfter
- Before 1
- 텍스트와 표 중심의 복잡 화면 -

- Before 2
- 중첩 폼과 긴 목록 -

- Before 3
- 지도 미연동 상태 -
- After 1
- 대화형 상담 + 핵심 정보 요약 -

- After 2
- 간결한 필터와 단축 액션 -

- After 3
- 지도 기반 직접 선택 -
- -## 디자인 원칙 - -- 기본 텍스트는 16~18px 이상으로 유지 -- 버튼과 컨트롤은 최소 44~48px 높이로 설계 -- 명확한 레이블과 단계적 흐름 제공 -- 텍스트와 배경 대비 확보 -- 오류 메시지는 원인과 다음 행동을 함께 안내 - -## 의견과 기여 - -- 버그나 실행 문제는 [Issues](https://github.com/yungi0816/gov-admin-uiux-example/issues)에 남겨주세요. -- 공공/레거시 UI 개선 아이디어나 화면 설계 피드백은 [Discussions](https://github.com/yungi0816/gov-admin-uiux-example/discussions)에 남겨주세요. -- 문서, 접근성, 스크린샷 설명 개선 PR은 환영합니다. +- 상담관리 사례: 기존 Before/After 이미지와 공개용 구조 정리 완료 +- 경계조정 사례: 리뷰 문서와 익명화 코드 구조 추가 완료 +- 실제 업무 데이터, 운영 테이블명, 내부 API명은 포함하지 않음 diff --git a/cases/README.md b/cases/README.md new file mode 100644 index 0000000..4fdddbf --- /dev/null +++ b/cases/README.md @@ -0,0 +1,15 @@ +# Case Studies + +공개 가능한 UI/UX 개선 사례를 기능 테마 단위로 관리합니다. + +| Case | 문서 | 상태 | +| --- | --- | --- | +| 01. 상담관리 및 지도 선택 흐름 | [consult-management](consult-management/README.md) | 이미지 및 구조 정리 완료 | +| 02. 경계조정 파일-필지 연계 검증 | [boundary-adjustment](boundary-adjustment/README.md) | 리뷰 문서와 익명화 코드 추가, 스크린샷 추가 대기 | + +## 추가 규칙 + +- 새 기능은 `cases//README.md`를 먼저 작성합니다. +- 화면 이미지는 해당 사례의 `assets/` 폴더에 둡니다. +- 공개용 코드는 `source-sanitized/`에만 둡니다. +- 원본 업무 시스템 식별자는 사례 문서와 코드에 남기지 않습니다. diff --git a/cases/boundary-adjustment/README.md b/cases/boundary-adjustment/README.md new file mode 100644 index 0000000..5e40efd --- /dev/null +++ b/cases/boundary-adjustment/README.md @@ -0,0 +1,142 @@ +# Case 02. 경계조정 파일-필지 연계 검증 + +경계가 조정된 필지에 대해 파일과 필지를 함께 등록하고, 실제 필지별 조정 횟수를 도면 위에서 확인할 수 있게 만든 사례입니다. + +이 기능의 핵심은 화면을 새롭게 꾸미는 것이 아니라, 기존 공공 행정 시스템의 제약 안에서 사용자가 "이 파일이 어떤 필지와 연결되어 있는지", "해당 필지가 몇 번 조정되었는지", "도면상 위치가 맞는지"를 한 화면에서 검증할 수 있게 하는 것입니다. + +## 리뷰 요약 + +| 항목 | 내용 | +| --- | --- | +| 업무 문제 | 경계조정 파일과 실제 필지 연결 상태를 별도 목록과 도면을 오가며 확인해야 했음 | +| 사용자 리스크 | 잘못된 파일-필지 연결, 조정 이력 누락, 도면 확인 누락 가능성 | +| 시스템 제약 | 기존 JSP/jQuery 화면, 공통 테이블 UI, OpenLayers 지도 모듈, 권한/감사 로그, 기존 파일 저장 구조 유지 | +| 개선 방향 | 파일 등록, 필지 선택, 도면 하이라이트, 조정 횟수 시각화를 하나의 검증 흐름으로 연결 | +| 공개 방식 | 원본 식별자 제거 후 역할 중심 문서와 익명화 코드만 공개 | + +## 제약 조건 + +전체 시스템 UI를 마음대로 재설계할 수 없는 조건이었습니다. 공통 CSS와 기존 업무 화면 패턴을 유지해야 했고, 현업 사용자가 이미 익숙한 표 기반 입력 방식도 깨기 어려웠습니다. + +따라서 이 사례에서는 새로운 디자인 시스템을 도입하기보다 다음 판단을 우선했습니다. + +- 기존 등록 폼과 목록 구조는 유지하되, 지도와 선택 필지 목록을 같은 시야에 배치 +- 조회, 등록, 수정 상태를 명확하게 표시해 잘못된 저장을 방지 +- 파일을 선택하면 연결된 필지를 도면과 목록에 동시에 표시 +- 필지별 조정 횟수를 색상 단계로 표현해 검토 우선순위를 빠르게 판단 +- 투명도 조절을 제공해 기존 도면 판독을 방해하지 않도록 처리 + +## 사용자 흐름 + +```mermaid +flowchart TD + A["경계조정 파일 목록 조회"] --> B["도면에서 필지 확인"] + B --> C["필지 클릭 또는 목록 검색"] + C --> D["선택 필지 목록에 추가"] + D --> E["파일 업로드 및 설명 입력"] + E --> F["파일과 필지 연결 저장"] + A --> G["기존 파일 선택"] + G --> H["연결 필지 목록 조회"] + H --> I["도면에서 연결 필지 하이라이트"] + I --> J["필요 시 수정 모드로 전환"] +``` + +## 구현 구조 + +| 계층 | 책임 | +| --- | --- | +| JSP / JavaScript | 지도 초기화, 필지 선택/해제, 목록 렌더링, 등록/조회/수정 모드 관리 | +| Controller | 파일 업로드, 필지 목록 조회, 조정 횟수 조회, 저장/삭제 요청 진입점 | +| Service | 파일 저장과 필지 연결 저장의 트랜잭션 흐름, 입력값 정규화 | +| Repository / Mapper | 익명화된 파일 메타데이터, 파일-필지 연결, 도면 형상, 조정 횟수 조회 | +| Map Layer | 이전/확정 경계 도면을 읽어 색상, 라벨, 선택 상태로 표현 | + +## 데이터 흐름 + +```mermaid +sequenceDiagram + participant User as "User" + participant UI as "JSP + OpenLayers" + participant API as "Example Controller" + participant Service as "Boundary Service" + participant Store as "Example Data Store" + User->>UI: "파일과 필지 선택" + UI->>API: "등록 또는 수정 요청" + API->>Service: "입력 검증 및 저장 위임" + Service->>Store: "파일 메타데이터 저장" + Service->>Store: "파일-필지 연결 저장" + UI->>API: "조정 횟수 및 도면 조회" + API->>Store: "필지별 조정 횟수, 도면 형상 조회" + Store-->>UI: "익명화된 필지 속성, 형상, 조정 횟수" + UI-->>User: "색상 단계와 선택 하이라이트로 검증" +``` + +## 화면 검토 포인트 + +| 화면 요소 | 의도 | +| --- | --- | +| 등록/조회/수정 모드 배지 | 현재 작업 상태를 명확히 보여 저장 실수 방지 | +| 선택 필지 목록 | 도면에서 선택한 필지를 표로 재확인 | +| 도면 하이라이트 | 목록과 실제 경계 위치를 연결해 검토 | +| 조정 횟수 범례 | 조정이 반복된 필지를 우선 검토할 수 있게 지원 | +| 투명도 조절 | 색상 시각화가 기존 도면 판독을 가리지 않도록 보정 | +| 전체보기 지도 | 작은 업무 패널 안에서도 상세 위치를 확인할 수 있게 지원 | + +## 스크린샷 갤러리 + +이미지는 공개 전 주소, 필지번호, 사용자명, 내부망 URL이 보이지 않도록 마스킹한 뒤 아래 파일명으로 추가합니다. + + + + + + + + + + + + + + + + + + +
전체 화면조정 횟수 범례
+ Boundary adjustment overview
+ 파일 등록, 도면, 선택 필지 목록을 함께 확인 +
+ Adjustment count legend
+ 조정 횟수 색상 단계와 투명도 조절 +
연결 필지 확인수정 모드
+ Linked parcels
+ 등록 파일 선택 시 연결 필지를 목록과 도면에 표시 +
+ Boundary adjustment edit mode
+ 기존 파일의 연결 필지 목록을 수정 +
+ +## 공개용 소스 + +| 파일 | 설명 | +| --- | --- | +| [source-sanitized/java/BoundaryAdjustmentController.java](source-sanitized/java/BoundaryAdjustmentController.java) | 공개용 컨트롤러 구조 | +| [source-sanitized/java/BoundaryAdjustmentService.java](source-sanitized/java/BoundaryAdjustmentService.java) | 파일-필지 연결 저장 흐름 | +| [source-sanitized/java/BoundaryAdjustmentRepository.java](source-sanitized/java/BoundaryAdjustmentRepository.java) | 데이터 접근 책임 예시 | +| [source-sanitized/mapper/BoundaryAdjustmentMap.xml](source-sanitized/mapper/BoundaryAdjustmentMap.xml) | 익명화된 SQL 매퍼 예시 | +| [source-sanitized/web/boundary-adjustment.jsp](source-sanitized/web/boundary-adjustment.jsp) | 지도 선택과 모드 전환 중심의 JSP/JS 구조 | + +## 후속 개선 여지 + +현재 사례는 기존 시스템 제약 안에서 구현한 개선입니다. 제약이 완화된다면 다음 개선을 검토할 수 있습니다. + +- 지도, 파일 등록, 선택 필지 목록을 독립 컴포넌트로 분리 +- 등록/조회/수정 모드를 명시적인 상태 머신으로 관리 +- 필지 선택 이벤트와 저장 요청에 대한 프론트엔드 테스트 추가 +- 조정 횟수 색상 기준을 운영 설정으로 분리 +- 키보드 접근성과 스크린리더 레이블 보강 + +## 익명화 메모 + +이 문서와 공개용 소스는 실제 운영 테이블명, API명, 업무 코드, 패키지명, 필지 고유번호, 내부 지도 유틸명을 포함하지 않습니다. 공개 기준은 [../../docs/security-redaction.md](../../docs/security-redaction.md)를 따릅니다. diff --git a/cases/boundary-adjustment/assets/01-boundary-overview.png b/cases/boundary-adjustment/assets/01-boundary-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..2f0ca4697246c38bb7270c6b4ccea38c7fb825ea GIT binary patch literal 9162 zcmeHtYgm$5*FS2b9kqFCvOG~w&(mQnwK2~Enp2LMX?E}omL)3UAv_~s<0LbgQaNRX zij8^7V;QKZpjleu0na3ect}wZF%bw5_|r4jb6xM3|EKqQzs-mHy05+O>)vbawSH^u zy?=ZC=5yYC$5x}Q003Zz$4@`{0sz_r0D#u>&AOVNz1#mWrg>?heBDn0YWqzVHH}Sh zS1(rp;3Z+(#?@~$?JaRX1)u-`{m!qCR(EX0bpYTX%;QJbODRwR=LF$cbE-wC`T0Kk!* z#ae)3mkeD%hW6i`e;ed)F#Ih%_qa(GxR?;c~2u+ms0o4+CR^WxIwWe60R zzH#Vo+f0(^`svA7`i3W4AH07NBx-%?*h9+9tP14&qxMCSU< zLe73==gwl;=2Gchzz*jo{PqFFbSet@YyZ{7&g}z-PpvEGKnGkzuAahuT1F{x28P9I zu>nMAka^J0WKZWkRfp~Zf8OIZpwInC8@$y#Lr)s2B`21{D0&?b)$p*ldQ}-jT!$N$ z-pg|v8gJ8CAL_6*o0U6P_9XhD0AHG%uVkFRW&I6nG^xh*i-ok|`yA9!7t$xIu0jKpX7l}*b+*7>J-JBnaY3456skA$5;1B4XfG<8EpN!huGt%3F z-@Lzf@t%h+99D(MtIvNXsyYI*9H7-}P-URYP#{ zNa=V7HQLwoN^Q^@4U}KRFC|+rBAxpmk{v5+n6X9J)Vk(UlI4gTWb+gMB<;{>n630= zZHGc9bShpuVQWa9TQQ{djOC~GAF!$8<=}aI%;2aVJG-hoKg!{iRneOLj8ac8v|>=? zYNCiZuCmLkUKa=B(+B)AJ3gkjVhX(lB9*D}NNV{W?4FvD6l*rBk`$oukGQwukbZdkid_^*(q3`Ay?u7m!uqKq6K&}A7cxR)Jx+9ohgZv3w7l8xoocfMCyLC#>Eyr3gi|YwP zUk(Wr)Z8wBmE@yV+Mc0DYU81EfkJ~G zX^!l4Aloq0aZhQ+F2t)SpO!$9rT858e6Ae#XbFVC{Y%_|?yEFypXl7QP?~uyRyMHLoT~xS=RiEl z7?>-VHspbt-J>B@!+qN8Z|snVAI6tsXH&SZ#m1vlgSDgEgRLhgWzd(0G6TD;*{t#L;$+ejZ(2TMfAjdYLPPBzsTR;(^M>r> z&B7wjFfV~Gj#JI6+{8LQ__^bE6v15zod=RLY(bE zvHAq$a1O7*LB}Lz835YiyLh7|J%v+GsGKQyu+cX-=bEH+elu;TUvpE<=@=)-zFHs< z_Y0t~tFY@*#k<5X?8B9YsxUfXZSR#3$k?fzFgCJgm@!n~+k#lfz+jn!O)pUBhmskC zJ;fJIp}fHyzF001F<(xm5wr<{cb$>_@6&LKej%{26x`|mM&7p~ z3g-DR{nev3ZEw&_z7l0DZTLrV?1fBt=I12E=U`%zLBSYZS?%wc(WS|KE2U`sD)!+0a7pp{UYly9}u zq_|^EbQl+`!~ro=TCu6>tUB2(ODkVB zRKdc%aJ&}-kZ1);C1x7k3gQmtXzDLJQQ|BCZ)B-tDBs3D%*8DClr|ZfRYHpv=1h38J zaqd;yq7q4Cx`B?t;xp~_BX5IE_P@&vJ!G0;hlwwD`vdgT8C{V{KY0M$kG7>;<$C#_ z6`7I?L#&Ou8(zEX;?tL$a&Fku4Ux*5anbD3_?Vl=ho~l5J&mBsWP(o7B1n*gG6)Fgs97*90qZ21-90=gD2C1TF2^DCE zy5FSKS9M2BqlMq;cX<^sUX8nA65iMCT_;7Id=dsuaeubDXI3AdYJ~DIiw&fwMtJDY zJJ%pXj6~FqqRnN}fYk8ASJeZ>;gRRW;y;xIHND3L-6!XrMnYqEw|Z0I5iBqS2>4F3 zC-UyH9J{XE8S^d^G1ngWP@j_Q0QR3L&^MI^5k0>+^$$9hV$1P~|8^m;2+DOhMk()9 z?|%**g+p6{^j;z4PCvA8KSuZc?D55ue0K=ybkYp|RC-yA5l#%Xo?-Yzp(BDb$uyC; z63aLfrgtReJpl7+&mVfvqA#T1!qsb0t(YHfO&m43@)BA&Y=5!cxYZpYsoJg;gY?KP%uJ*5YhSL_UnFisiy-K#vI6qoF)HcGro?< zo4DCFY10@d*x1KvrR&zPXr5A7`XeoU=adZHrJKJNs@wno@B!_UK6$kJt!u`o0bllR z8EB_=kSg!ua`XWtGcBXN*<9PkjgX@N!s!g%t}EKFV@X;mR@x`6Y~^`7ruSv&9dgmx zwMgH({8l}DkQ=%Gs>ic2}X4Z*Nq zpV3`5HjO3|^nf>>@#J9~>9-qJibh-vpb4nu_~k<^T~J3a0nTSA>)$%iYcD7Wk3ULw ziNn4b2l8ozsTD=?>I5JTq5)kwz866xrAzqbbu<8zA(cp9`mBy5)*FWTR`vE%HUxxW=GH9ok$34h`JvZnialwvr^#0#P$@|rtx%Dp zB&k0rgU!Y!4N^=|$)uM`>T6}|sES=~nI9DrL`({?5T-hGso=5Fvx@RU&%pcyLO#V( zagduFod_H{_nF}fe5>8w(jRJ4ymU+B>V?a zoY6=hK8w{l^1NzsENLeNCNIMCmK?j<27V#OoG)ahf3H}pLHaXhf~y1w^fJ|`S`4Zk zuTU=hIR%2vKVa67YdqQNBbP2K2ng#%r_^l&dehCVxecH-i2;1KOaOyv|%C%52JjRodnNHXm8R1O?CKf1Dpfa@+-XP4fBOI7tKdB9q1i->e)0r1NqC8PYR8`#sSv-dvMlp2 zh$KUL@S#ep6@zy)`YeD09ICN!F?e%AS(J@3tG*De zh=TIpQI7t8uI^}cpQ|FNRsm4T45!&$4Kt2r%l89v(Agq~COE=5YbLly0GHMZZzXwo zV!S0Y*(&9UwEqOKd{pLF!m-2F>0Ob^{F9HY+}3#o3AHrft(#A>p0)Uq7#~$uI2pH5 zzCtggZ62+Y(#%|rxPus8?BvjpXids!RK89$b==%At1x-e3-@JSZ|#5A z{D{q1Y4as|Xl9JhXw8puO!-UjvS4Rxbi+UHSRnD&wwtpXISsM&p#!NB+y0dS(@j}D zZd5So3Ccc(egj+B$%CmEZ$x_?r#g_fYYflf;MN<)>IKgyIW~EeRAd(=RlsvN)%_#AuR4i1DiMH&m1jDtS<(nX!Kq~lw{1c~nwTjf z?ly4`tKc+_X{5hCBwbcigbNg5>tD07b^?5jzsv-e;R8@dV(>hVtq%^D=-8SLDZuP@ zs*o=zk}-LVJv`KPV|cPa(&xX~&K#xk(OFlC+QwP0`L9T@rN%by7jK6peiKkJ#$Bc9 zREhSHy+w~}L;>-&RT|itjg}P-A6J}L5+;EP?3pmY6vgbYi#j75|4#q_Xs0FD*HEb$ z-uVEGp7u#Vd%0%@O-DnfP7VKCL$blQwd8f0^?=8~ zo)F8JIDVaCAziUPdiVsvn{7wOjH6&GvO2&Q$(a43SqtlgIYRaNx(rTFk$*76rO9K| zvd&hZ$60j5(kvKvHZcVgu20GyiJ4C2*FF(<6HaBsYBN&kgpz~>;HHJoEwj5)AU9!n^g^d!+M_ExBCC`USck-}jn|QAa+*3l?Kot! zmi!I3h$f$zyZ5+J;c~q1zJaUFPCvHD{w!WKswMw)5T_Z!Q!~Vs{|@mp(wQ0Dquer3 zGpNERh=pP!BVTb zx+NI}Jzfa0t3cg27aPThGeN(S_dZWrYzQ@G*f7hml1KCJe+O_?$Vv_jqhKbXz8Q*M(9 z08J56jak9CLjGJELeQ8w--DT&%z5?$FMv%cdPs2>aIJG+tQ8%rm8OH%Sm4GjuGg;M zDdDAVxSbhX5w;*l+(d64pYYA7&Al;3YvV;@;L7KUv60D~hIk2v9)gNu(qJsKfDC*5 z8UhQ+KU-)XmX`F+x}a-!4cNL;!v>03WoSrsaEo=7G75NIJ0T7nL5$tQx@SKzrK7+@ zKBVbDpIlZ<0;%=3SN61N(yJR^=agUhgl*V2A6pLMuIM)7Uo zIPrdx5`G@x4q&A7CA*3iQG3P+?8=>e|L-kn#Q)< z6hg#UZAO9U%l#1Z{>0T8)8?q6e@AX)8ZuYlVui2MzmW|Gx-AV>I zuxc5IT_k8K0%GUWYR-UT#35W8tXNQAg+8>{Kprl zCG;@KuQ@yxsYl+z?CpY?4g1t0UUd5GMgPuf!`cq#*0f1hX@`?J#LWX8bD3Y#zL53$ z!xQ#@(|~Vq+Fsindo@?#W19!P#C9&if%n27B-LarlcH`O>?5lm&ad7O<))hP6)XB8 zS9%EMg`iwf3G^FM>CDf-JO2uW#LBE#eaTd9A2!{SKPccnQJHycJ;%Z14f=2WA9e?s zhc(|QG?&&me$2lL>Yg?6WIkuuVg&B!g-3}+>SMW;!vuI@f1y2hKiQKc0FzcogXBSw z^pJK!x$Ml5)?y3O)~Td$1Gsb*hT_gS&_d|A`UfTpleMOtDx!Cj%2|S#H-c^_N@?xg zUDAmJzTHA2d)ufalK~C3=6#-!+dz?MVf@JNl1~kv{cSz@@%5BQ69Fc$>Q;$$+Sntm zX`doq8sOALR?UZ_dic$bkAj$~V%q5vu_Iq9NG}MW9>P$5BNNaIl*A(CvE0a47Q|79 zWJ*1HH8>Vy@y!B;UQ+DTVr#?Due3OJW7K{dQE+4a z@&O^QVK_&5ry05c8wxS4d(12C$sd?&xuV1(PU8zzOfZl~Oz@IN??M2j z14zEJA^=im4DS5U;e=+vt1sics@WZ6RO5sO6^27@WX$Y?%$d{@p!1IkN@QoQnQ91= zuL((YgtMChmx#}oh7-N3J*AdX^LfX5GN!8Ip(xsGo{Br##tBP`oA^T+4dF!ezbp#F zuz_%sgQcJVs7BG=F$zY)w=w40?d?M>axNINkB>z=H@4~?RQgAu#-%S=SX`gjrh;zF zweBY?CsxM&;te>I{svNFn2|sXPgVEIg-Avslo(0$uToJz&abj;gj|iz@K|FCW~$$^ zvcqiv1GYljx8T)J6_f*0?<+_gPvIX>_O?jFO+(u@uB(rWPXja2kLDfIPv}F(DhXxE z6j-#bR$ym;rs&%*Vjg1b@a0!91? zH)rhF7<~QQrS6+mj()8o)P1NCg@D=?%1vpKT+^X13Mk;mRi8XW!PmCR6DG7IBm%=_sp zvd8DqpBp~NE><#@X$_>(6MZWh+8SjGBjz>rXCFv0R-ewZ(JUJ>wLTg=jpHz_lp&1s?+;DZjwOxLkNChHaap zc=N`Y@H~GIn_CH1ASgzCV`#DlL51JV2Uo&N1)Q#LC7fOx^s85s=KR5aq@(2B)0zqC zvZ+?u7|y9~(7y^Hnfc+z*f3xpI`tUQN;-l~pi6k}%3!}tntG51GJ-$&k2e$%>eK?o zjwNTs_@9q$g{fI3R*i!j5&<3p*@lbyp@|7?X=Jrow@M$PMHfKlGv@Vd(w=m9f#|DD zs6_Hf?~nR$(lMJ1LJ^=ZWV89k&hy;r1a6uB$2?%xpHDF_MgU-}_Q~V})p59TSnND3 z?6Bk$zL*kDI%JdKed|B}q(>}!0Ae%@$$xJY|GsbkyVl@uF#P`tPp)OC@`WnZh86%2 zxA^i_@% literal 0 HcmV?d00001 diff --git a/cases/boundary-adjustment/assets/02-adjustment-legend.png b/cases/boundary-adjustment/assets/02-adjustment-legend.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5f6d9346fa6f9a06e4d31f5a750f6955057501 GIT binary patch literal 9085 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 Wm&=MKDUZwmes?;5vi3y4t^Wph3cN`G literal 0 HcmV?d00001 diff --git a/cases/boundary-adjustment/assets/03-linked-parcels.png b/cases/boundary-adjustment/assets/03-linked-parcels.png new file mode 100644 index 0000000000000000000000000000000000000000..dfec4c35df24e10ee180b6cd788f5d73115f7ca8 GIT binary patch literal 8597 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
    ;&MR-xg#71to3`hxM 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 literal 0 HcmV?d00001 diff --git a/cases/boundary-adjustment/assets/04-edit-mode.png b/cases/boundary-adjustment/assets/04-edit-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..37458acd6f0d44dd3f3f8fdc4785f5e67d8425ed GIT binary patch literal 7971 zcmeHMYgkfgyIyK(Wri~yEKPB)nW?@GS~+SS(Xp~~W@?!tHa1csqIp0?z;eoIDxH~T zI(fjdgNj0iAcBbHmsTcN3J3^fmZ%711Oy0dn)&vh{d-^6{;}uBy4JPU`>yxBpZk5* z^W4|-T!=Xk?Xt*y5dZ)#yLWAm1pxa&0I<8Wz|r<*<>D{-wwqmUZ1nd4iL-jj_An0~ zxjzyBS}UFBj?cF}FU;PRmL0N_R6y*)CnFhwb0Rc!1la!iF?$c2^p zMqFCBIfk^o`@+I?u{*b=J{>OoW8tOiTV3mZx=_8jb*aOe7u)wN4%w2r(vg2%qP$Vt zL~b(G)C9rN@`YYUs~a1Q)AyOO8t5y$25i;JKoF}IVh5DEl+VlsmRn~JTIbXN(0x9t zAlz{hZzh2>=Tg*lcxmt+NBXy(1ie zh=@{0ptSV!u$`T|jS2hjZ1(;$@gI8rA2`KmQK`i+2F4roAiN3P0t=fPXH$`|*9WbG zmuIUW%ayS^*uFV`-gHD9(?NOGen{vXLQHz!Crc@+e5zXls754?lMo9Hw1|Z-{FF8; zd;tQnr;M!Fb&d9u&dGQ0ee+Yi7Bzoz96oPS;r!PJP{&E^Skdf5*D!a*`dfDv)%j-s zdGmMI1RiA3pjB61&gF_04GP?5Jb}?xrt_fHXoBV29UodZjBK=jLm~e3`)=zs`kwu} z7Z9g?1q|Z~SZP;Rsv3A*AxSJHogI-uN-QSAK_mke?CbJH{>qL{_G1-b+``k~ge64m z(mJww1;zXM9;5(KTSn6H`wCamiOLSS<3>GCPL5X5$OK##f%CrIOgi_-|^_ zhvbQ1UeT%-X&*hx>GyVo>H3YUTjb|3f>nY}wFY=!FnX|*)U~|!r{C7Q)|gv9?phUF zd}QHr-q9;TtX))GOJ8f5D=i+cwd@L2-{$N#W0`VXLG#`q{qu^h=kBzbDfCu-k$L>} z=uN!?fa){Pn_Pcs|M$C=*R@-hFhENQR!h0__spFd0qAlvk>LF=LZ|ICPgCx zOzyF4-qy^XVNSQZAQ~TyI-2=sag^Dw` zqhPKM0La6Vd6Qd#0sAMlI%w7UVWso8Zd1L4bBxhVQ6u#dt@Ge1>oqM)qHfrKpw#iT zlA#8|7Dr(0cYQJayfoISB48i%o9e?E7?<^rO3(sWGZm0v6G|ORE5~v6d7seN>qJ068SYf!T&P9BO{3qQ}*rjtomK^>+sEw&;eow znlx9?(emy*rX5iN6!xjsMS#5K;tN~OZWog>avN!CR+Br){lv8QHm~|6KRF>6VA`0L zri#xeHJuytnO#a35tYjX^ZCzHleJL6W>U}yQ{o>QG%$M?;|P#@pz|jW?b>G2=B`{q z<3&z>A8{Pl*VD1z=A*^(n;P8<>X$vu>kNa~s@-SA?7EVc#fGVE8zyJH4# z&|xNYS?ZeeXWpm6Tti+E7nQv$AsWm5hZTjtJ5m^I8}zFP za;;K~f32nxBn!Z7&N5l%-VY1~&5h)mR!m(8RMV&JNt4XsOJdE2wBZ9lt z8jTkf^6x*8vc`kUPe6Ed{p$%iI$Hu{hoIf+?t^Z~0|?kzUQav3O)Yym0Dgdal3J-c zg$~7g%g!!`HUs&X)r=7iT`L7LcECprtamU#Q1pA z48!hq!Mw@a15zj6?#pOyR&SDvhniZyjp)nyK92^PTM%a_6X_EPP zhjHOIFeXD&txp7=f(naHH+^W^sdX53>KVU|{nU^V>L|)@aF!p=u%Ep$8(6MfEykw^ z{1lN5WJ|pS=qhoN4rfrSnrVrBTxT#THRCqw&8tT0NsA6LH*~FFV{=?!Q zAUz%TloY=Lq5F&i)0tkctN+`71dlqfo5MmvXPD)QLnv)ajN8ADCpF4;Q@46k4I%>nF3WPcE-H{9`;AHoO|vFap*@EXl}s9}%ESD*e0{cc%Z z;N#rRN{ZiU<0H6c@lMT+%R%8wc_oyN31z+Cik8R`bpCmoas7ltH&OyERe5c#N99-a zvd^X@K?cvuVn{|}R}oQMsn|vXmuTV@rp7psiGNF_WrUeEU&V!TXGfw<+_E5$t|8Nu z*~JeLAK;@t-S9KttQG0m6<`8&sPI0m{#SlwVi2jw;8BJfHH3oB8usz2Tcz@BQjSSD z!V}FyxauV`FeeTXOwR)7o_59B6tn9KaIZP4RgDUX(1WHD-0Gah8r<1RB9%y{_+u>- zGes$I8Q>$^N1iysDS8n7qZqECN`69S<bskodhmG`_~cR8n3f*r0%EN zx&2YCi#PPQODHIIQ~BGMjU3gjkOhn7psA8@zrO5IT>o5XIY@@J#wAf!NUA}Ow*8@( zA;o0>%#cL2%I^wIE4{QybBv4LmeSrn70%)8O3?3u5QNvw$$h43jz1FO!6fJ2gO#Py) z3U5gJj?nlcl$~s`6cwrGk~(N?97hXs>!ERtz}A4wkcCgo@W-rGI%s((t~2sv@B)2n z_}6k-0~#^J$PUMF-enSoY<~ef4UPGd6gjLHie@@N|G|*88vJRXC&%-bq;X;W z-sCis+4;oaUWYTHb^YO*Rd-s9f9Ia&)AV{<(^npW2pbWiD@i&QHC<^=%ljOe?epLt z6f^ShdmzfhEqZ7usL8u%b8&Y9Xeyt8Ooi(6-6z7Djyml(z3L?fP7xcy0c&4)wWaQOKS*>G8RYA@%)VPmE_w3kUHQ zNsn*WC++ZGNl!Ikm>mEB10gzb;dY18kV*@onr zfA%u^E*L~vEKg(d&hL~y(yTpJ&eF7ql$}y9at8`Gs;WHrMrHr;)1dj&d+q5|ae04I zYfyB`#qV72+mO7B!hao6srFRAwmd~5Yi|jB8CP|j_KYJeesPvY=mrZbG38o~A1Niv z>_6UMhnAS$4MqJRRsAXQk4`E3?=`G%0POo-b>%3ZBaPBUj|`uV6!iWMAiwudm>LC_pw7D1^ZvWE<&c8!GgPms76#+c~sb5G0h_M zYC{rdcOnDjo$%VK17E#qCco+2$j4CT7L}~{Sx0ZQ>?A}zPM55P- z++0oWd1dLW)s!FC{wfq(+S1KNc0O_?Qn=o^|i)e2=Q!YpsiA3D23QYD827fSX8H*mc129UVM)W)qR=i#1DS9TqMM>9A%Tnz9sAX4X=ci1E)pw~^pp&u z+QUzori!K&O>L0wYF1z}_l|Ot;%Pjh@L`VQ5hP!$6jiF`I3LsEn!1NCC#~XrIU2%I zSK2b7aMWntm(ej1ymtIv0O!Y6!gz3B6E}{$$w&p)+(M8vbF7E=4#2q5AoO#?ilzb{ zf+)Bzhkq2l+QeOBpLgG|vw(YaaY zkE?3N8CGW;l`$zYknwy?mrIObtZfZ}K3+at>QaYP+ zW!jI2xOiIG@0TaZuVo1Yc~21ZhdJC@T_P#odgHVtAUp&;U~>%YJx0S-RPuCPVd$iX z+e-#AKQ-D7|DB2yRI7(H!B3hGqjNMV8#VtD+%-KjpY2$(5qS`iJrv+NHeTnD$NZm026oG+MioN+1ghM<1U|VX8b6k%AG*?C5mZ$|=ON ziHE8*lt~NsftAfkKm8u8uaWCsgm2&u<_n5+DMyATn;}hHZnIT-8Y;l}ArIR?cRU=J zfa{A4!tJHu!JZ?!vmw$uj&6~9WB1IP86V5uUSw4F)k6?sl~h=~Dez9bYeUv+Ck3jJ zYg~D0)j)n>+Un6}V)#wncy8;;(FFuV;snD>zj9;^TZP+g5*Gb3QQZuGrMsG+>>YB& z@R@0;Sla<(rSNlOnuWdenwRddV*F|?`fk_;0X%R29cnV7W+*4>B`YUEzFm7`nz}>V z=^l=E%8tS9wbil*Nz5f5 zdY=ekb8eT!qbnJDXGP4*VD{urw`8;NmAc%QFflrf=$~M)RsEtP>DY}s5xD`a`6IyPdUw-MJov-h^!_VXLzq->h zF8)D!Xwz|%FnF!`En+hyMA=cp3yQ_wD@L}y(9f`*Y1SqZgmj9&>834h>Np(%vZ}Jv z1oZjWcP$iuQBDh~uev3YsWS*WQT897^LJ2^llyuEmJ{^j1vv?BcvDB%ILY^F%SBDY z(JCEfuZyJJ+3CrS_F68(lBiVV=-N=?gnWW<3wIN5;AB`|29{4q params) { + model.addAttribute("params", params); + return "cases/boundary-adjustment"; + } + + @RequestMapping(value = "/files", method = RequestMethod.GET) + public String listFiles(Model model, @RequestParam Map params) { + model.addAttribute("stageAFileList", boundaryAdjustmentService.findFiles(params, "STAGE_A")); + model.addAttribute("stageBFileList", boundaryAdjustmentService.findFiles(params, "STAGE_B")); + return "jsonView"; + } + + @RequestMapping(value = "/file-parcels", method = RequestMethod.GET) + public String listFileParcels(Model model, @RequestParam Map params) { + model.addAttribute("list", boundaryAdjustmentService.findLinkedParcels(params)); + return "jsonView"; + } + + @RequestMapping(value = "/parcels/search", method = RequestMethod.GET) + public String searchParcels(Model model, @RequestParam Map params) { + model.addAttribute("list", boundaryAdjustmentService.searchParcels(params)); + return "jsonView"; + } + + @RequestMapping(value = "/map/boundaries", method = RequestMethod.GET) + public String listBoundaryGeometry(Model model, @RequestParam Map params) { + model.addAttribute("boundaryList", boundaryAdjustmentService.findBoundaryGeometry(params)); + return "jsonView"; + } + + @RequestMapping(value = "/adjustment-counts", method = RequestMethod.GET) + public String listAdjustmentCounts(Model model, @RequestParam Map params) { + model.addAttribute("list", boundaryAdjustmentService.findAdjustmentCounts(params)); + return "jsonView"; + } + + @RequestMapping(value = "/files", method = RequestMethod.POST) + public String registerFile( + Model model, + @RequestParam Map params, + @RequestParam("file") MultipartFile file) { + + if (isBlank(params.get("projectId")) || file == null || file.isEmpty()) { + model.addAttribute("errorMessage", "Invalid request."); + return "jsonView"; + } + + boundaryAdjustmentService.registerFileWithParcels(file, params); + model.addAttribute("successMessage", "Saved."); + model.addAttribute("fileList", boundaryAdjustmentService.findFiles(params, null)); + return "jsonView"; + } + + @RequestMapping(value = "/file-parcels", method = RequestMethod.PUT) + public String updateFileParcels(Model model, @RequestParam Map params) { + if (isBlank(params.get("fileMapId")) || isBlank(params.get("projectId"))) { + model.addAttribute("errorMessage", "Invalid request."); + return "jsonView"; + } + + boundaryAdjustmentService.replaceLinkedParcels(params); + model.addAttribute("successMessage", "Updated."); + return "jsonView"; + } + + @RequestMapping(value = "/files", method = RequestMethod.DELETE) + public String deleteFile(Model model, @RequestParam Map params) { + if (isBlank(params.get("fileMapId"))) { + model.addAttribute("errorMessage", "Invalid request."); + return "jsonView"; + } + + boundaryAdjustmentService.deleteFile(params); + model.addAttribute("successMessage", "Deleted."); + return "jsonView"; + } + + private boolean isBlank(Object value) { + return value == null || String.valueOf(value).trim().isEmpty(); + } + + @SuppressWarnings("unused") + private Map auditContext(String action) { + Map audit = new HashMap(); + audit.put("action", action); + audit.put("workCode", "REDACTED"); + return audit; + } +} diff --git a/cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentRepository.java b/cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentRepository.java new file mode 100644 index 0000000..2a77ae6 --- /dev/null +++ b/cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentRepository.java @@ -0,0 +1,41 @@ +package gov.example.admin.boundary; + +import java.util.List; +import java.util.Map; + +import org.springframework.web.multipart.MultipartFile; + +/** + * Data-access boundary for the public example. + * + * Method names describe responsibilities. Mapper namespaces, schema owners, + * and production table/view names are intentionally omitted. + */ +public interface BoundaryAdjustmentRepository { + + List> selectFileList(Map params); + + List> selectLinkedParcelList(Map params); + + List> selectSearchParcelList(Map params); + + List> selectBoundaryGeometry(Map params); + + List> selectAdjustmentCountList(Map params); + + Object insertFileMetadata(MultipartFile file, Map params); + + Object insertBoundaryFileRecord(Map params); + + void insertFileMapping(Map params); + + void insertParcelLink(Map params); + + void deleteParcelLinks(Map params); + + void deleteFileRecord(Map params); + + void deleteFileMapping(Map params); + + void deleteStoredFile(Map params); +} diff --git a/cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentService.java b/cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentService.java new file mode 100644 index 0000000..200c911 --- /dev/null +++ b/cases/boundary-adjustment/source-sanitized/java/BoundaryAdjustmentService.java @@ -0,0 +1,119 @@ +package gov.example.admin.boundary; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.web.multipart.MultipartFile; + +/** + * Sanitized service example for the boundary-adjustment review case. + * + * The original implementation had to stay compatible with an existing + * map-based parameter style. This example keeps that constraint visible while + * isolating the normalization and file-parcel linking responsibilities. + */ +public class BoundaryAdjustmentService { + + private final BoundaryAdjustmentRepository repository; + + public BoundaryAdjustmentService(BoundaryAdjustmentRepository repository) { + this.repository = repository; + } + + public List> findFiles(Map params, String stageCode) { + params.put("stageCode", stageCode); + return repository.selectFileList(params); + } + + public List> findLinkedParcels(Map params) { + return repository.selectLinkedParcelList(params); + } + + public List> searchParcels(Map params) { + return repository.selectSearchParcelList(params); + } + + public List> findBoundaryGeometry(Map params) { + return repository.selectBoundaryGeometry(params); + } + + public List> findAdjustmentCounts(Map params) { + return repository.selectAdjustmentCountList(params); + } + + public void registerFileWithParcels(MultipartFile file, Map params) { + validateAllowedExtension(file.getOriginalFilename()); + + Object storedFileId = repository.insertFileMetadata(file, params); + params.put("storedFileId", storedFileId); + + Object fileMapId = repository.insertBoundaryFileRecord(params); + params.put("fileMapId", fileMapId); + + repository.insertFileMapping(params); + insertParcelLinks(params, normalizeParcelIds(params.get("parcelIdList"))); + } + + public void replaceLinkedParcels(Map params) { + repository.deleteParcelLinks(params); + insertParcelLinks(params, normalizeParcelIds(params.get("parcelIdList"))); + } + + public void deleteFile(Map params) { + repository.deleteParcelLinks(params); + repository.deleteFileRecord(params); + repository.deleteFileMapping(params); + repository.deleteStoredFile(params); + } + + private void insertParcelLinks(Map params, List parcelIds) { + for (String parcelId : parcelIds) { + params.put("parcelId", parcelId); + repository.insertParcelLink(params); + } + } + + private List normalizeParcelIds(Object rawValue) { + if (rawValue == null) { + return Collections.emptyList(); + } + + List parcelIds = new ArrayList(); + + if (rawValue instanceof String[]) { + for (String parcelId : (String[]) rawValue) { + addIfPresent(parcelIds, parcelId); + } + } else if (rawValue instanceof Iterable) { + for (Object parcelId : (Iterable) rawValue) { + addIfPresent(parcelIds, String.valueOf(parcelId)); + } + } else { + String value = String.valueOf(rawValue); + for (String parcelId : value.split(",")) { + addIfPresent(parcelIds, parcelId); + } + } + + return parcelIds; + } + + private void addIfPresent(List parcelIds, String parcelId) { + if (parcelId != null && !parcelId.trim().isEmpty()) { + parcelIds.add(parcelId.trim()); + } + } + + private void validateAllowedExtension(String fileName) { + if (fileName == null) { + throw new IllegalArgumentException("File name is required."); + } + + String lowerName = fileName.toLowerCase(); + if (!lowerName.endsWith(".sdb") && !lowerName.endsWith(".dat")) { + throw new IllegalArgumentException("Only SDB and DAT files are allowed."); + } + } +} diff --git a/cases/boundary-adjustment/source-sanitized/mapper/BoundaryAdjustmentMap.xml b/cases/boundary-adjustment/source-sanitized/mapper/BoundaryAdjustmentMap.xml new file mode 100644 index 0000000..62e41b4 --- /dev/null +++ b/cases/boundary-adjustment/source-sanitized/mapper/BoundaryAdjustmentMap.xml @@ -0,0 +1,115 @@ + + + + + + + + INSERT INTO EXAMPLE_FILE_PARCEL_LINK ( + FILE_MAP_ID, + PARCEL_ID, + PROJECT_ID, + REGISTERED_AT, + REGISTERED_BY + ) VALUES ( + #fileMapId#, + #parcelId#, + #projectId#, + CURRENT_TIMESTAMP, + #userId# + ) + + + + DELETE FROM EXAMPLE_FILE_PARCEL_LINK + WHERE FILE_MAP_ID = #fileMapId# + + + + + + + + + + + diff --git a/cases/boundary-adjustment/source-sanitized/web/boundary-adjustment.jsp b/cases/boundary-adjustment/source-sanitized/web/boundary-adjustment.jsp new file mode 100644 index 0000000..25e9521 --- /dev/null +++ b/cases/boundary-adjustment/source-sanitized/web/boundary-adjustment.jsp @@ -0,0 +1,270 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> + + + + + + + +
    + + + +
    +
    + 등록 모드 + +
    + 경계조정 횟수 +
    5회 이상
    +
    4회
    +
    3회
    +
    2회
    +
    1회
    + +
    +
    + + + + + + + + + + + + + +
    선택된 필지 목록
    소재지지번지목면적관리
    + + + +
    diff --git a/cases/consult-management/README.md b/cases/consult-management/README.md new file mode 100644 index 0000000..4857d48 --- /dev/null +++ b/cases/consult-management/README.md @@ -0,0 +1,54 @@ +# Case 01. 상담관리 및 지도 선택 흐름 개선 + +레거시 행정 시스템의 상담관리 화면과 지도 기반 필지 선택 흐름을 정리한 사례입니다. 기존 사용자의 업무 습관을 크게 흔들지 않으면서 상담 내용 확인, 핵심 정보 접근, 지도 선택 흐름을 더 명확하게 만드는 데 초점을 두었습니다. + +## 리뷰 요약 + +| 항목 | 내용 | +| --- | --- | +| 문제 | 표와 중첩 폼 중심 화면에서 상담 흐름과 지도 선택 맥락이 분리됨 | +| 제약 | JSP/Java 기반 화면, 기존 공통 CSS, 현업 사용자의 익숙한 조작 방식 유지 필요 | +| 개선 | 상담 핵심 정보를 더 빨리 파악하고 지도 선택 흐름으로 자연스럽게 이어지도록 정리 | +| 공개 범위 | 스크린샷과 익명화된 소스 구조만 포함 | + +## Before & After + + + + + + + + + + +
    BeforeAfter
    + Before 1
    + 텍스트와 표 중심의 복잡 화면 +

    + Before 2
    + 중첩 폼과 긴 목록 +

    + Before 3
    + 지도 흐름이 업무 화면과 분리된 상태 +
    + After 1
    + 상담 맥락과 핵심 정보 요약 +

    + After 2
    + 간결한 필터와 단축 액션 +

    + After 3
    + 지도 기반 직접 선택 +
    + +## 설계 판단 + +- 기존 테이블형 화면의 인지 구조를 완전히 버리지 않고, 사용자가 이미 알고 있는 조작 위치를 유지했습니다. +- 지도 선택 기능은 별도 도구처럼 보이지 않도록 상담 흐름과 연결했습니다. +- 폼 입력 오류를 줄이기 위해 사용자가 직접 좌표나 식별자를 입력하는 상황을 최소화했습니다. +- 화면의 미적 변화보다 업무 중 확인해야 하는 정보의 우선순위를 재정렬했습니다. + +## 공개용 소스 + +`source-sanitized/`는 원본 시스템 코드가 아니라 구조 설명용 파일입니다. 실제 테이블명, API명, 패키지명, 업무 코드는 제거되어 있습니다. diff --git a/images/after_1.png b/cases/consult-management/assets/after_1.png similarity index 100% rename from images/after_1.png rename to cases/consult-management/assets/after_1.png diff --git a/images/after_2.png b/cases/consult-management/assets/after_2.png similarity index 100% rename from images/after_2.png rename to cases/consult-management/assets/after_2.png diff --git a/images/after_3.png b/cases/consult-management/assets/after_3.png similarity index 100% rename from images/after_3.png rename to cases/consult-management/assets/after_3.png diff --git a/images/before_1.png b/cases/consult-management/assets/before_1.png similarity index 100% rename from images/before_1.png rename to cases/consult-management/assets/before_1.png diff --git a/images/before_2.png b/cases/consult-management/assets/before_2.png similarity index 100% rename from images/before_2.png rename to cases/consult-management/assets/before_2.png diff --git a/images/before_3.png b/cases/consult-management/assets/before_3.png similarity index 100% rename from images/before_3.png rename to cases/consult-management/assets/before_3.png diff --git a/cases/consult-management/source-sanitized/README.md b/cases/consult-management/source-sanitized/README.md new file mode 100644 index 0000000..9f2c11c --- /dev/null +++ b/cases/consult-management/source-sanitized/README.md @@ -0,0 +1,5 @@ +# Sanitized Source + +상담관리 사례의 공개용 코드 구조입니다. 기존 저장소에 있던 예시 파일을 사례 폴더 아래로 이동해 이후 기능과 같은 규칙으로 관리합니다. + +이 폴더의 파일은 원본 업무 시스템의 전체 구현이 아니라 공개 가능한 구조 참고용입니다. diff --git a/pom.xml b/cases/consult-management/source-sanitized/pom.xml similarity index 100% rename from pom.xml rename to cases/consult-management/source-sanitized/pom.xml diff --git a/src/main/java/pplis/biz/dao/ConsultDao.java b/cases/consult-management/source-sanitized/src/main/java/gov/example/biz/dao/ConsultDao.java similarity index 100% rename from src/main/java/pplis/biz/dao/ConsultDao.java rename to cases/consult-management/source-sanitized/src/main/java/gov/example/biz/dao/ConsultDao.java diff --git a/src/main/java/pplis/biz/service/ConsultService.java b/cases/consult-management/source-sanitized/src/main/java/gov/example/biz/service/ConsultService.java similarity index 100% rename from src/main/java/pplis/biz/service/ConsultService.java rename to cases/consult-management/source-sanitized/src/main/java/gov/example/biz/service/ConsultService.java diff --git a/src/main/java/pplis/biz/service/impl/ConsultServiceImpl.java b/cases/consult-management/source-sanitized/src/main/java/gov/example/biz/service/impl/ConsultServiceImpl.java similarity index 100% rename from src/main/java/pplis/biz/service/impl/ConsultServiceImpl.java rename to cases/consult-management/source-sanitized/src/main/java/gov/example/biz/service/impl/ConsultServiceImpl.java diff --git a/src/main/java/pplis/biz/web/ConsultController.java b/cases/consult-management/source-sanitized/src/main/java/gov/example/biz/web/ConsultController.java similarity index 100% rename from src/main/java/pplis/biz/web/ConsultController.java rename to cases/consult-management/source-sanitized/src/main/java/gov/example/biz/web/ConsultController.java diff --git a/src/main/resources/pplis/sqlmap/ConsultMap.xml b/cases/consult-management/source-sanitized/src/main/resources/gov/example/sqlmap/ConsultMap.xml similarity index 100% rename from src/main/resources/pplis/sqlmap/ConsultMap.xml rename to cases/consult-management/source-sanitized/src/main/resources/gov/example/sqlmap/ConsultMap.xml diff --git a/src/main/webapp/WEB-INF/jsp/gisMap/viewLxMapPop.jsp b/cases/consult-management/source-sanitized/src/main/webapp/WEB-INF/jsp/map/mapSelectionPopup.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/gisMap/viewLxMapPop.jsp rename to cases/consult-management/source-sanitized/src/main/webapp/WEB-INF/jsp/map/mapSelectionPopup.jsp diff --git a/src/main/webapp/css/consult.css b/cases/consult-management/source-sanitized/src/main/webapp/css/consult.css similarity index 100% rename from src/main/webapp/css/consult.css rename to cases/consult-management/source-sanitized/src/main/webapp/css/consult.css diff --git a/src/main/webapp/js/calendar/consult.js b/cases/consult-management/source-sanitized/src/main/webapp/js/calendar/consult.js similarity index 100% rename from src/main/webapp/js/calendar/consult.js rename to cases/consult-management/source-sanitized/src/main/webapp/js/calendar/consult.js diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b356fa7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# Documentation + +이 폴더는 공개 가능한 범위에서 사례를 설명하기 위한 기준 문서입니다. + +| 문서 | 역할 | +| --- | --- | +| [review-framework.md](review-framework.md) | 기능 사례를 전문가 리뷰 형식으로 정리하는 공통 구조 | +| [security-redaction.md](security-redaction.md) | 공공 시스템 정보를 공개 저장소에 남기지 않기 위한 익명화 기준 | + +## 작성 원칙 + +- README는 사례 탐색과 요약에 집중합니다. +- 상세한 판단 근거는 각 `cases/*/README.md`에 둡니다. +- 원본 업무 시스템의 실제 식별자는 공개 문서에 쓰지 않습니다. +- 구현 코드는 재사용 가능한 제품 코드가 아니라 리뷰를 위한 익명화 예시로 관리합니다. diff --git a/docs/review-framework.md b/docs/review-framework.md new file mode 100644 index 0000000..267106b --- /dev/null +++ b/docs/review-framework.md @@ -0,0 +1,39 @@ +# 전문가 리뷰 작성 프레임워크 + +각 사례는 단순히 "화면을 바꿨다"가 아니라 어떤 제약 안에서 어떤 판단을 했는지 설명해야 합니다. 특히 공공 행정 시스템은 화면 일관성, 권한, 감사 로그, 기존 사용자 숙련도, 배포 리스크 때문에 자유로운 리디자인이 어려운 경우가 많습니다. + +## 권장 구조 + +| 섹션 | 작성 목적 | +| --- | --- | +| 문제 정의 | 사용자가 실제로 겪는 혼선, 오류 가능성, 확인 비용을 설명 | +| 운영 제약 | 변경 불가한 UI 규칙, 기술 스택, 권한/로그, 데이터 구조를 명시 | +| 개선 방향 | 제약 안에서 선택한 화면 배치, 상태 표시, 확인 흐름을 설명 | +| 구현 구조 | 프론트엔드, 컨트롤러, 서비스, 데이터 접근 계층의 책임을 분리 | +| 검증 관점 | 사용자가 무엇을 더 명확히 확인할 수 있게 되었는지 정리 | +| 보안 처리 | 공개 저장소에서 제거하거나 치환한 내부 식별자를 명시 | +| 후속 개선 | 제약이 완화될 때 개선 가능한 항목을 현재 구현과 분리 | + +## 리뷰 톤 + +좋은 리뷰 문서는 과장하지 않습니다. "최신 UI로 바꿨다"보다 "기존 공통 컴포넌트를 유지하면서 사용자가 필지와 파일의 연결 상태를 한 화면에서 검증할 수 있게 했다"처럼 검증 가능한 문장으로 씁니다. + +## 제약을 설명하는 방식 + +제약은 변명이 아니라 설계 조건입니다. 다음처럼 표현합니다. + +| 피해야 할 표현 | 권장 표현 | +| --- | --- | +| 전체 시스템 때문에 원하는 대로 못 바꿨다 | 공통 JSP/CSS와 기존 업무 화면 규칙을 유지해야 했기 때문에 화면 체계를 벗어나지 않는 범위에서 개선했다 | +| 예쁜 UI를 만들 수 없었다 | 미관보다 현업 사용자의 확인 정확도, 기존 학습 비용, 운영 리스크를 우선했다 | +| 임시로 붙였다 | 기존 지도 모듈과 등록 폼 사이에 상태 동기화 계층을 추가했다 | + +## 코드 공개 기준 + +공개 코드는 원본을 그대로 옮기지 않습니다. 구현 판단을 설명할 수 있을 만큼의 구조만 남깁니다. + +- 실제 API 경로 대신 역할 중심 샘플 경로 사용 +- 실제 테이블/스키마 대신 `EXAMPLE_*` 명칭 사용 +- 업무 코드값은 `STAGE_A`, `STAGE_B`처럼 의미 중심으로 치환 +- 감사 로그, 권한 체크, 공통 유틸은 목적만 설명 +- 실데이터, 사용자명, 필지번호, 좌표는 포함하지 않음 diff --git a/docs/security-redaction.md b/docs/security-redaction.md new file mode 100644 index 0000000..ecd90ac --- /dev/null +++ b/docs/security-redaction.md @@ -0,0 +1,35 @@ +# 보안 및 익명화 기준 + +이 저장소는 공공 행정 시스템 개선 사례를 설명하기 위한 공개 자료입니다. 따라서 실제 운영 환경을 식별할 수 있는 이름, 경로, 코드, 데이터는 남기지 않습니다. + +## 비공개 처리 항목 + +| 항목 | 처리 방식 | +| --- | --- | +| 실제 테이블, 뷰, 스키마명 | `EXAMPLE_FILE`, `EXAMPLE_FILE_PARCEL_LINK`, `EXAMPLE_GIS_BOUNDARY_VIEW`처럼 역할 중심 명칭으로 치환 | +| 실제 API 경로 | `/example/boundary-adjustment/...` 같은 샘플 경로로 치환 | +| 실제 업무 코드 | `STAGE_A`, `STAGE_B`, `DOC_TYPE_BOUNDARY` 등 의미 중심 값으로 치환 | +| 실제 패키지명 | `gov.example.admin.*` 형태로 치환 | +| 실제 사용자, 권한 그룹, 감사 코드 | 권한/감사 처리 목적만 설명 | +| 실제 필지 고유번호, 주소, 좌표 | 샘플 값 또는 설명 문장으로 대체 | +| 내부 지도/공통 유틸 | 호출 목적만 남기고 내부 구현은 제외 | + +## 공개 가능한 항목 + +- 사용자가 수행하는 업무 단계 +- UI 상태 전환 방식 +- 지도와 목록이 동기화되는 구조 +- 파일 등록과 필지 연결의 책임 분리 +- 조정 횟수 시각화 같은 UX 판단 +- 익명화된 레이어 구조와 데이터 흐름 + +## 검토 체크리스트 + +공개 전 다음 항목을 확인합니다. + +- 실제 스키마명, 테이블명, 뷰명이 남아 있지 않은가 +- 실제 API 경로나 컨트롤러 매핑이 남아 있지 않은가 +- 업무 코드값이 내부 시스템을 추정할 수 있을 만큼 구체적이지 않은가 +- 이미지에 기관명, 사용자명, 주소, 필지번호, 좌표, 내부망 URL이 보이지 않는가 +- 로그 코드, 권한 그룹 코드, 파일 저장소 구조가 그대로 노출되지 않았는가 +- 코드가 운영 시스템에 바로 연결될 수 있는 수준의 접속 정보나 쿼리를 포함하지 않는가 From 4820117ef7c4ed1d0274e45eeaa97867a92c96ff Mon Sep 17 00:00:00 2001 From: yu_no Date: Thu, 21 May 2026 09:38:16 +0900 Subject: [PATCH 2/2] Strengthen public admin GIS case presentation --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ LICENSE | 14 ++++++++++++++ README.md | 19 +++++++++++++++---- README_KOR.md | 27 ++++++++++++++++++++++++--- 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 LICENSE diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e46c0eb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable portfolio documentation changes are recorded here. + +## 0.1.0 - 2026-05-21 + +### Added + +- Added the `Public Admin GIS Modernization` project title. +- Added a case-study structure under `cases/`. +- Added the consultation-management case with before/after screenshots. +- Added the boundary-adjustment case for file-parcel registration and map-based adjustment-count review. +- Added sanitized Java, JSP, and SQL mapper examples for the boundary-adjustment flow. +- Added public redaction rules for production table names, API paths, work codes, parcel identifiers, and internal system names. +- Added placeholder image slots for masked boundary-adjustment screenshots. +- Added a portfolio review license. + +### Changed + +- Moved existing sample source and images out of the repository root into case-specific folders. +- Reworked the root README into a navigation and evidence summary layer. + +### Security + +- Removed production-identifying names from public example code and documentation. +- Documented the repository's redaction checklist before publishing additional screenshots. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..773e7bc --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Portfolio Review License + +Copyright (c) 2026 yungi0816 + +This repository is published for portfolio review and technical evaluation. + +You may view the documentation, screenshots, and sanitized source examples for +review purposes. + +You may not copy, redistribute, republish, or use the materials in another +project without written permission. + +The repository intentionally excludes production system identifiers, real +operational data, internal API names, and protected public-sector information. diff --git a/README.md b/README.md index 5e19cd3..384276d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ -# Government Admin UI/UX Modernization Examples +# Public Admin GIS Modernization [한국어 README](README_KOR.md) -This repository documents UI/UX improvement work for legacy public-sector administration systems. It does not expose the original production system. Each case keeps the workflow, design decisions, and sanitized implementation structure while removing internal table names, API names, organization identifiers, and real operational data. +Case-study repository for improving map-based work flows and legacy UI structure in public-sector administration systems. -The goal is not a full visual redesign. These examples show how to improve task accuracy and reviewability inside strict constraints: JSP/Java screens, shared legacy CSS, audit logging, authorization rules, existing map components, and fixed business procedures. +The repository does not expose the original production system. Each case keeps the workflow, design decisions, review notes, and sanitized implementation structure while removing internal table names, API names, organization identifiers, work codes, parcel identifiers, and real operational data. + +## Technical Evidence + +| Area | Evidence | +| --- | --- | +| Legacy Java/JSP modernization | UI improvements are documented within JSP, jQuery, Java controller/service/repository, and mapper constraints | +| GIS-enabled admin workflow | OpenLayers-based parcel selection and boundary review flows are documented as separate case studies | +| Public-sector system constraints | Audit logging, authorization, fixed business procedures, and shared UI rules are treated as design constraints | +| User-centered operation design | Before/after screenshots and review notes explain how the work reduces confirmation cost and input mistakes | +| Security-aware documentation | Sanitized source and redaction rules prevent production identifiers from being published | ## Cases @@ -20,6 +30,7 @@ The goal is not a full visual redesign. These examples show how to improve task | Documentation index | [docs/README.md](docs/README.md) | | Review framework | [docs/review-framework.md](docs/review-framework.md) | | Security redaction policy | [docs/security-redaction.md](docs/security-redaction.md) | +| Changelog | [CHANGELOG.md](CHANGELOG.md) | ## Repository Structure @@ -43,4 +54,4 @@ Each case owns its screenshots under `cases//assets/`. Add images usi ## License -No explicit license is currently provided. Treat the repository as a portfolio-style documentation example unless a license is added. +This repository is published for portfolio review. See [LICENSE](LICENSE). diff --git a/README_KOR.md b/README_KOR.md index 78be320..c4137a1 100644 --- a/README_KOR.md +++ b/README_KOR.md @@ -1,10 +1,20 @@ -# 국가 행정 시스템 UI/UX 개선 사례 +# Public Admin GIS Modernization [English README](README.md) -운영 중인 공공 행정 시스템에서 실제 제약을 전제로 진행한 UI/UX 개선 사례를 정리한 저장소입니다. 이 저장소는 원본 업무 시스템을 공개하지 않고, 화면 구조, 사용자 흐름, 설계 판단, 익명화된 코드 흐름만 남깁니다. +공공 행정 업무시스템의 지도 기반 업무 흐름 개선과 레거시 UI 현대화 사례를 정리한 저장소입니다. -핵심 관점은 전면 재설계가 아닙니다. 기존 JSP/Java 기반 화면, 공통 CSS, 권한/감사 로그, 지도 컴포넌트, 업무 절차를 유지해야 하는 조건에서 사용자가 더 정확하게 확인하고 덜 실수하도록 개선한 기록입니다. +원본 운영 시스템을 공개하지 않고, 화면 구조, 사용자 흐름, 설계 판단, 리뷰 문서, 익명화된 코드 흐름만 남깁니다. 실제 테이블명, API명, 기관명, 업무 코드, 필지 식별자, 운영 데이터는 제거했습니다. + +## 기술 근거 + +| 영역 | 근거 | +| --- | --- | +| Java/JSP 레거시 개선 | JSP, jQuery, Java Controller/Service/Repository, SQL Mapper 제약 안에서 개선 구조를 문서화 | +| GIS 기반 업무 흐름 | OpenLayers 기반 필지 선택, 도면 확인, 경계조정 검증 흐름을 별도 사례로 정리 | +| 공공/행정 시스템 이해 | 권한, 감사 로그, 기존 공통 UI, 고정 업무 절차를 설계 제약으로 다룸 | +| 실사용자 중심 UX | Before/After 이미지와 리뷰 문서로 확인 비용, 입력 오류, 업무 흐름 단절을 설명 | +| 보안 의식 | 원본 식별자를 제거한 공개용 소스와 익명화 기준 문서 포함 | ## 사례 목록 @@ -13,6 +23,16 @@ | 01 | 상담관리 및 지도 선택 흐름 | 상담 화면을 업무 맥락 중심으로 재정리하고 지도 기반 선택 흐름을 보강 | [cases/consult-management](cases/consult-management/README.md) | | 02 | 경계조정 파일-필지 연계 검증 | 파일과 필지를 함께 등록하고 조정 횟수를 도면 위에서 확인 | [cases/boundary-adjustment](cases/boundary-adjustment/README.md) | +## 기술 검토 포인트 + +| 영역 | 설명 | +| --- | --- | +| 업무 흐름 분석 | 현업 담당자가 실제로 확인해야 하는 정보와 오류 가능성이 높은 지점을 기준으로 화면 재배치 | +| 지도/GIS 연계 | 도면, 필지 목록, 등록 파일 상태를 연결해 시각적 검증 흐름 구성 | +| 레거시 호환 | 기존 JSP 화면, 공통 CSS, 테이블 UI, 권한 처리, 파일 저장 흐름을 유지하며 개선 | +| 계층 분리 | 화면, 컨트롤러, 서비스, 데이터 접근 계층의 책임을 공개용 코드로 분리해 설명 | +| 공개 보안 | 운영 테이블명, API 경로, 업무 코드, 필지번호를 익명화 | + ## 공개 기준 국가/공공 시스템 성격상 아래 항목은 공개 저장소에 남기지 않습니다. @@ -78,4 +98,5 @@ - 상담관리 사례: 기존 Before/After 이미지와 공개용 구조 정리 완료 - 경계조정 사례: 리뷰 문서와 익명화 코드 구조 추가 완료 +- 변경 이력, 라이선스 문서 추가 - 실제 업무 데이터, 운영 테이블명, 내부 API명은 포함하지 않음