docs(core,extensions): 확장 개발자 문서 9세트 집필과 생성기 정합 보강
S1 파일럿 3종(sirsoft-board · sirsoft-gdpr · sirsoft-admin_basic)에 이어
결제·본인인증 동형군 6종의 AGENTS.md · README.md · docs/ 5문서를 집필했다.
확장을 고치려는 쪽이 매번 src/ 를 훑어 구조를 재발견하지 않도록, 설계 의도와
확장점(발행·구독 훅)·수정 시 동반 의무·금지 패턴을 코드 근거로 서술했다.
생성기(ExtensionDocScaffolder)에서 표를 무의미하게 만들던 세 결함을 함께 고쳤다.
getLayoutExtensions 기본 구현이 돌려주는 절대경로가 파일 목록과 중복돼 로컬
머신 경로가 커밋 문서에 실리던 문제, getNotificationDefinitions 를 'key'/'event'
로 읽어 모든 행이 '-' 로 찍히던 문제, getSettingsLayout 절대경로가 정규화 없이
노출되던 문제다. 셋 다 예외를 남기지 않고 표만 조용히 망가뜨리므로
ExtensionDocContractTest 에 각각의 되돌림 red 를 확인한 단언을 두었다.
템플릿의 extensions/{id}/ 는 모듈·플러그인의 발행과 반대 방향(오버라이드)이라
별도 블록(template-overrides)으로 분리했다.
sirsoft-admin_basic 의 컴포넌트·핸들러·레이아웃 문서를 코어 docs/ 에서 그 템플릿
소유로 이관하고, 남은 참조 6축을 재는 가드를 추가했다. 이관 후 남은 옛 경로는
오류가 아니라 헛걸음으로만 나타나 드러나지 않는다. 의 상대 링크가
한 단계 얕아 공유 docs/ 대신 를 가리키던 문제도 함께 고쳤다.
README 상단의 확장명 이미지 배지를 평문 H1 로 바꿨다( 지시 2026-08-31).
루트 README.md · README.ko.md 도 같은 기준을 적용했다. 정보 배지는 유지한다.
sirsoft-gdpr: 회원탈퇴로 자동 철회된 동의가 관리자 동의 이력 화면의 출처 필터로
걸러지지 않던 문제를 고쳤다. Repository 가 'withdraw' 리터럴을 직접 UPDATE 에
싣는데 그 값이 ConsentSource enum 에 없어, 화면 필터 옵션·라벨 어느 쪽에도
도달하지 못했다. 어휘를 enum 단일 출처로 모으고 ko·en·ja 라벨과 필터 옵션을
함께 채웠으며, 어휘 대조 테스트의 모집단에 Repository 를 편입했다.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# Admin Basic — 에이전트 가이드
|
||||
|
||||
> 이 문서는 이 템플릿을 수정하는 에이전트·확장개발자를 위한 것입니다. 도입 검토·운영 관점은 [README.md](README.md) 를 보세요.
|
||||
|
||||
## TL;DR (5초 요약)
|
||||
|
||||
```text
|
||||
1. 유형: 템플릿 (sirsoft-admin_basic) — 코어·모든 번들 확장의 관리자 화면을 그리는 유일한 admin 템플릿
|
||||
2. 확장 방식: 필수 컴포넌트(35개, config/template.php)만 써서 레이아웃 JSON 작성 — 다른 admin 템플릿으로 교체돼도 동작 보장
|
||||
3. 건드리면 안 되는 것: `Icon` 컴포넌트로 AdminSidebar 아이콘 렌더(반드시 `I`+FontAwesome 클래스), `_admin_base` 슬롯 구조를 임의로 재배치
|
||||
4. 작업 위치: `templates/_bundled/sirsoft-admin_basic` — 활성 디렉토리 직접 수정 금지
|
||||
5. 반영: `php artisan template:update sirsoft-admin_basic --force`
|
||||
```
|
||||
|
||||
## 1. 이 확장은 무엇인가
|
||||
|
||||
<!-- @intent START -->
|
||||
G7 이 기본 제공하는 유일한 admin 타입 템플릿입니다 — 코어 관리자 화면(대시보드·사용자·역할·
|
||||
설정·확장 관리 등)뿐 아니라 **모든 번들 모듈/플러그인의 관리자 레이아웃**(`resources/layouts/
|
||||
admin/`)이 이 템플릿의 베이스(`_admin_base`)를 extends 하고 이 템플릿의 컴포넌트로 그려집니다.
|
||||
그래서 이 템플릿의 공개 계약(필수 컴포넌트 35개, `_admin_base` 슬롯 구조)을 깨면 코어가
|
||||
아니라 **전체 번들 확장의 관리자 화면**이 동시에 영향을 받습니다 — 다른 번들 확장 하나를
|
||||
고치는 것과는 파급 범위가 다릅니다.
|
||||
|
||||
**설계 원칙**: 모듈/플러그인 개발자가 "이 컴포넌트만 쓰면 다른 admin 템플릿으로 바꿔도
|
||||
안전하다"는 보장을 받도록, 필수 컴포넌트 목록(config/template.php)을 이 템플릿 하나가 아니라
|
||||
**admin 템플릿이라면 지켜야 할 계약**으로 취급합니다 — 이 템플릿에만 있는 편의 컴포넌트를
|
||||
필수 목록에 넣지 않습니다.
|
||||
|
||||
**의도적으로 하지 않는 것**: 방문자용(user) 화면은 이 템플릿의 범위가 아닙니다(`sirsoft-basic`
|
||||
소관). 또한 컴포넌트 Props 전체 레퍼런스는 이 문서가 다시 나열하지 않습니다 — 코어
|
||||
`docs/frontend/component-props*.md` 가 SSoT 이고, 이 문서는 이 템플릿에서만 유효한 계약
|
||||
(필수 컴포넌트·AdminSidebar·SlotContainer·베이스 레이아웃 구조)만 다룹니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 2. 디렉토리 지도
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update sirsoft-admin_basic --force` (빌드 불필요) |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `src/handlers/` | 템플릿 전용 액션 핸들러 | `php artisan template:build` → `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `editor-spec.json` | 레이아웃 편집기 스펙 | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `editor-spec/` | 분할 편집기 스펙 | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `tests/` | 테스트 | 변경 범위만 필터 실행 |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
|
||||
## 3. 핵심 흐름
|
||||
|
||||
<!-- @intent START -->
|
||||
**새 관리자 화면 렌더**: 방문자가 `/admin/{path}` 접근 → `routes.json` 이 경로를 레이아웃
|
||||
이름으로 매핑 → 그 레이아웃이 `extends: _admin_base` 로 사이드바·헤더·Toast·콘텐츠 슬롯을
|
||||
상속 → `slots.content` 에 정의된 컴포넌트(대개 `PageHeader`+`DataGrid`/`Card`/`Form`
|
||||
조합)가 데이터소스 API 를 호출해 화면을 채웁니다. 이 흐름은 코어 화면과 번들 확장 화면이
|
||||
완전히 동일합니다 — 확장이 관리자 화면을 추가할 때 이 템플릿을 복제하지 않고 자기
|
||||
`resources/layouts/admin/*.json` 에서 그대로 `_admin_base` 를 extends 합니다.
|
||||
|
||||
**사이드바 접힘 상태 복원**: 페이지 로드 → `src/index.ts` 부트스트랩이 `initSidebar()` 직접
|
||||
호출(레이아웃 `init_actions` 아님) → localStorage 값을 `_global.sidebarCollapsed` 에 반영 →
|
||||
`_admin_base.json` 의 사이드바 영역 className 표현식이 그 값을 읽어 접힘 스타일 적용.
|
||||
|
||||
**로케일 전환**: 코어가 로케일을 바꾸면 이 템플릿의 `initTemplate()` 재등록 진입점이 호출되어
|
||||
핸들러 맵을 다시 등록합니다(§docs/handlers.md "부트스트랩"). 사이드바 접힘 등 1회성 부팅
|
||||
작업은 이 진입점에 섞지 않습니다 — 로케일 전환마다 중복 실행되면 안 되기 때문입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 4. 확장점
|
||||
|
||||
<!-- @generated:extension-points-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 확장점 | 수 | 상세 |
|
||||
|---|---|---|
|
||||
| 제공 컴포넌트 | 125개 | [제공 컴포넌트](docs/components.md#제공-컴포넌트) |
|
||||
| 레이아웃 | 145개 | [레이아웃 목록](docs/layouts.md#레이아웃-목록) |
|
||||
| 전용 핸들러 | 17개 | [템플릿 전용 핸들러](docs/handlers.md#템플릿-전용-핸들러) |
|
||||
| 확장 오버라이드 | 0개 | [확장 오버라이드](docs/layouts.md#확장-오버라이드) |
|
||||
<!-- @generated:extension-points-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
이 템플릿은 훅을 발행/구독하지 않습니다 — 관리자 화면 확장은 훅이 아니라 **레이아웃 확장
|
||||
오버라이드**(`extensions/{module-identifier}/*.json`, 지금은 0개)와 **컴포넌트 재사용**
|
||||
(공통 컴포넌트를 그대로 쓰는 것) 두 경로로만 이뤄집니다. 다른 확장의 관리자 화면 UI 를
|
||||
이 템플릿에서만 다르게 그리고 싶다면 첫 번째 경로를, 새 화면 유형(예: 새로운 카드 스타일)이
|
||||
필요하면 `src/components/composite/` 에 컴포넌트를 추가하고 `components.json` 에 등록하는
|
||||
쪽을 씁니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 5. 수정 시 동반 의무
|
||||
|
||||
- [ ] `_bundled` 에서만 수정하고 `php artisan template:update sirsoft-admin_basic --force` 로 반영
|
||||
- [ ] manifest version 상향 시 `package.json` · `package-lock.json` 동기화 + CHANGELOG 기재
|
||||
- [ ] 레이아웃 JSON 변경 시 빌드 없이 update 만 — 신규 Tailwind 클래스는 빌드된 CSS 에 존재하는지 확인
|
||||
- [ ] TSX/TS 변경 시 `--production` 재빌드 후 `dist/` 커밋 (sourceMappingURL 잔존 금지)
|
||||
- [ ] 다국어 키 추가 시 ko·en 동시 반영 + 번들 ja 언어팩 증분 동기화
|
||||
- [ ] 필수 컴포넌트(35개) 의 Props 시그니처를 깨는 변경은 전체 번들 확장 관리자 화면에 영향 — 변경 전 `src/components/{basic,composite}/` 의 실사용처를 넓게 확인
|
||||
- [ ] `_admin_base.json` 슬롯 구조(`content` 슬롯 등) 변경 시 그 슬롯에 의존하는 모든 화면(145개 레이아웃 대다수) 영향 검토
|
||||
- [ ] AdminSidebar 의 `MenuItem`/`AdminSidebarProps` 인터페이스 확장 시 이 문서의 §docs/components.md "AdminSidebar 상세" 동기화
|
||||
|
||||
## 6. 금지 패턴
|
||||
|
||||
<!-- @intent START -->
|
||||
| 금지 | 올바른 사용 | 이유 |
|
||||
|---|---|---|
|
||||
| `AdminSidebar` 메뉴 아이콘을 `Icon name="home"` 식으로 렌더 | `I` 컴포넌트 + FontAwesome 클래스 문자열(`icon` 필드가 이미 `"fas fa-home"` 형태) | 메뉴 API 가 클래스 문자열을 내려주므로 `Icon`(IconName enum) 으로 받으면 렌더되지 않는다 |
|
||||
| 필수 컴포넌트 목록 밖의 이 템플릿 전용 컴포넌트를 모듈 레이아웃에서 사용 | 필수 컴포넌트(config/template.php) 만 사용 | 다른 admin 템플릿으로 교체 시 그 화면만 깨진다 |
|
||||
| 사이드바 접힘 상태를 레이아웃 `init_actions` 로 매번 복원 | 템플릿 부트스트랩(`src/index.ts`)에서 1회 복원 | `init_actions` 는 화면 진입마다 재실행되어 불필요한 반복 처리가 된다 |
|
||||
| `_admin_base` 를 상속하는데 로그인 화면처럼 `initTheme`/메뉴 초기화를 다시 호출 | `_admin_base` 상속 화면은 이미 초기화된 전역 상태를 그대로 사용 | 중복 호출은 낭비이며, 두 초기화 지점의 결과가 어긋나면 화면 간 상태 불일치가 생긴다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 7. 테스트 실행
|
||||
|
||||
<!-- @generated:test-commands START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 종류 | 개수 | 위치 |
|
||||
|---|---|---|
|
||||
| PHPUnit | 0개 | — |
|
||||
| Vitest | 203개 | `vitest.config.ts` |
|
||||
| Playwright | 8개 | `tests/Playwright` |
|
||||
| 시나리오 매니페스트 | 2개 | `tests/scenarios` |
|
||||
|
||||
```bash
|
||||
# Vitest (확장 디렉토리에서) (PowerShell)
|
||||
cd templates/_bundled/sirsoft-admin_basic && powershell -Command "npm run test:run -- <대상>"
|
||||
|
||||
# Playwright E2E (Bash)
|
||||
npx playwright test templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/<대상>.spec.ts
|
||||
|
||||
```
|
||||
|
||||
무필터 전체 실행은 금지되어 있습니다 — 변경 범위에 걸리는 대상만 지정해 실행합니다.
|
||||
<!-- @generated:test-commands END -->
|
||||
|
||||
## 8. 문서 목차
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
@@ -0,0 +1,180 @@
|
||||
# Admin Basic
|
||||
|
||||
**G7 템플릿 · sirsoft-admin_basic**
|
||||
그누보드7 기본 관리자 템플릿
|
||||
|
||||
<!-- @generated:badges START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-1.0.8-0066FF?style=flat-square" alt="version 1.0.8">
|
||||
<img src="https://img.shields.io/badge/type-%ED%85%9C%ED%94%8C%EB%A6%BF-555555?style=flat-square" alt="type 템플릿">
|
||||
<img src="https://img.shields.io/badge/G7-%3E%3D7.0.10-1F883D?style=flat-square" alt="G7 >=7.0.10">
|
||||
<img src="https://img.shields.io/badge/license-MIT-8250DF?style=flat-square" alt="license MIT">
|
||||
</p>
|
||||
<!-- @generated:badges END -->
|
||||
|
||||
---
|
||||
|
||||
[소개](#소개) · [주요 기능](#주요-기능) · [동작 방식](#동작-방식) · [요구 사항](#요구-사항) · [설치](#설치) · [제공 컴포넌트](#제공-컴포넌트) · [사용 방법](#사용-방법) · [다른 확장과의 연동](#다른-확장과의-연동) · [문서](#문서) · [트러블슈팅](#트러블슈팅) · [변경 이력](#변경-이력) · [라이선스](#라이선스)
|
||||
|
||||
---
|
||||
|
||||
## 소개
|
||||
|
||||
<!-- @intent START -->
|
||||
G7 이 기본 제공하는 관리자(admin) 템플릿입니다. 코어 관리자 화면뿐 아니라 설치된 모든
|
||||
모듈/플러그인의 관리자 화면이 이 템플릿의 컴포넌트와 베이스 레이아웃을 그대로 사용합니다 —
|
||||
확장을 설치하면 그 확장의 관리자 UI 도 자동으로 이 템플릿의 디자인(사이드바·헤더·색상·다크
|
||||
모드)을 따릅니다.
|
||||
|
||||
이 템플릿은 사용자(방문자)용 화면을 그리지 않습니다 — 방문자 화면은 `sirsoft-basic` 템플릿의
|
||||
몫입니다. 또한 완전히 다른 디자인의 관리자 화면이 필요하면 이 템플릿을 고치는 대신 같은
|
||||
컴포넌트 계약(필수 컴포넌트 35개)을 구현하는 새 admin 템플릿을 만드는 것이 원칙입니다 — 그래야
|
||||
기존 확장들의 관리자 화면이 새 템플릿에서도 깨지지 않습니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 주요 기능
|
||||
|
||||
<!-- @intent START -->
|
||||
| 영역 | 설명 |
|
||||
|---|---|
|
||||
| 관리자 셸 | 사이드바(계층형 메뉴, 접힘 지원)·헤더·다크 모드·다국어 전환을 갖춘 공통 화면 골격(`_admin_base`) |
|
||||
| 컴포넌트 125개 | HTML 래핑 39개(basic) + UI 패턴 캡슐화 80개(composite) + 페이지 구조 5개(layout) + 모달 1개 |
|
||||
| 필수 컴포넌트 계약 | 35개 컴포넌트만 사용하면 다른 admin 템플릿으로 교체해도 화면이 보장되는 모듈 호환성 기준 |
|
||||
| 레이아웃 145개 | 대시보드·사용자·역할·메뉴·설정·확장 관리·스케줄·활동/알림 로그 등 코어 관리자 화면 전체 |
|
||||
| 확장 관리 UI | 모듈/플러그인/템플릿 설치·활성화·업데이트·삭제와 레이아웃 편집기(코드 편집 + 실시간 미리보기) |
|
||||
| 본인인증(IDV) 챌린지 | 관리자 민감 작업에 걸리는 본인인증 화면 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 동작 방식
|
||||
|
||||
<!-- @intent START -->
|
||||
```mermaid
|
||||
flowchart TD
|
||||
base["_admin_base (사이드바·헤더·Toast·콘텐츠 슬롯)"] --> list["목록 화면 (PageHeader+DataGrid+Pagination)"]
|
||||
base --> detail["상세 화면 (PageHeader+Card)"]
|
||||
base --> form["폼 화면 (Form+FormField)"]
|
||||
base --> settings["설정 화면 (TabNavigation+_tab_*)"]
|
||||
auth["인증 화면 (admin_login 등, _admin_base 미상속)"]
|
||||
errors["에러 화면 (errors/*, 독립 레이아웃)"]
|
||||
```
|
||||
|
||||
인증 화면(로그인/비밀번호 찾기·재설정)과 에러 화면은 의도적으로 `_admin_base` 를 상속하지
|
||||
않습니다 — 로그인 전이거나 정상 화면 렌더링 자체가 불가능한 상황이라 사이드바·헤더 같은
|
||||
"이미 로그인된 관리자" 전제의 UI 를 보여줄 수 없기 때문입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 요구 사항
|
||||
|
||||
<!-- @generated:requirements START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| G7 코어 | `>=7.0.10` |
|
||||
| PHP | `^8.2` |
|
||||
<!-- @generated:requirements END -->
|
||||
|
||||
## 설치
|
||||
|
||||
<!-- @generated:install START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
```bash
|
||||
# 번들 설치 (코어에 동봉된 소스에서 설치)
|
||||
php artisan template:install sirsoft-admin_basic
|
||||
|
||||
# 활성화
|
||||
php artisan template:activate sirsoft-admin_basic
|
||||
|
||||
# 업데이트 (번들 소스 기준 강제 반영)
|
||||
php artisan template:update sirsoft-admin_basic --force
|
||||
```
|
||||
|
||||
저장소: https://github.com/gnuboard/g7-template-sirsoft-admin_basic
|
||||
<!-- @generated:install END -->
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:settings-summary START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 125개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 39개 |
|
||||
| `composite` | 80개 |
|
||||
| `layout` | 5개 |
|
||||
| `modals` | 1개 |
|
||||
<!-- @generated:settings-summary END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
운영자가 직접 켜고 끄는 설정 항목은 없습니다 — 이 표는 확장(모듈/플러그인) 개발자가 레이아웃을
|
||||
만들 때 참고하는 컴포넌트 인벤토리입니다. 전체 목록·Props 상세는
|
||||
[docs/components.md](docs/components.md) 와 코어
|
||||
[component-props.md](../../../docs/frontend/component-props.md) 를 참고하세요.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 사용 방법
|
||||
|
||||
<!-- @intent START -->
|
||||
**새 확장의 관리자 화면 만들기**: 확장의 `resources/layouts/admin/*.json` 에서
|
||||
`"extends": "_admin_base"` 로 베이스를 상속하고, `slots.content` 에 필수 컴포넌트만으로 화면을
|
||||
구성합니다. 필수 컴포넌트 목록 밖의 컴포넌트를 쓰면 다른 admin 템플릿으로 교체됐을 때 그
|
||||
화면만 깨집니다.
|
||||
|
||||
**사이드바 메뉴 등록**: 확장이 `getAdminMenus()` 로 메뉴를 선언하면 이 템플릿의 `AdminSidebar`
|
||||
가 자동으로 계층에 반영합니다 — 이 템플릿을 직접 수정할 필요가 없습니다.
|
||||
|
||||
**레이아웃 실시간 편집**: `/admin/templates/sirsoft-admin_basic/edit` 에서 레이아웃 편집기로
|
||||
관리자 화면 자체를 코드 편집 + 실시간 미리보기로 수정할 수 있습니다(운영 환경에서는 신중하게
|
||||
사용).
|
||||
<!-- @intent END -->
|
||||
|
||||
## 다른 확장과의 연동
|
||||
|
||||
<!-- @generated:integrations START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**이 확장이 의존하는 확장**
|
||||
|
||||
없음 — 코어만으로 동작합니다.
|
||||
|
||||
**이 확장에 의존하는 확장** (이 확장을 비활성화하면 함께 영향을 받습니다)
|
||||
|
||||
없음.
|
||||
<!-- @generated:integrations END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
formal 의존성이 양쪽 다 "없음"인 것은 이 템플릿이 코어만으로 동작하기 때문이지만, 실질적으로는
|
||||
**모든 번들 모듈/플러그인의 관리자 화면**이 이 템플릿의 필수 컴포넌트·`_admin_base` 계약에
|
||||
암묵적으로 의존합니다. 이 의존은 manifest 로 선언되지 않습니다 — 어느 admin 템플릿이든 같은
|
||||
계약(필수 컴포넌트 35개)만 구현하면 되므로, 확장이 "이 템플릿"이 아니라 "이 계약"에 의존하는
|
||||
형태이기 때문입니다. 이 템플릿의 필수 컴포넌트 Props 를 바꿀 때 영향 범위를 이 템플릿
|
||||
자신의 `dependencies` 목록으로는 알 수 없다는 뜻이며, 실제로는 활성 모듈/플러그인 전수의
|
||||
관리자 레이아웃을 확인해야 합니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 문서
|
||||
|
||||
<!-- @generated:docs-index START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 | 상태 |
|
||||
|---|---|---|
|
||||
| [docs/README.md](docs/README.md) | 문서 통합 목차와 실측 집계 | ✅ |
|
||||
| [docs/architecture.md](docs/architecture.md) | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
|
||||
| [docs/components.md](docs/components.md) | 템플릿이 제공하는 컴포넌트 | ✅ |
|
||||
| [docs/layouts.md](docs/layouts.md) | 레이아웃 목록과 라우트 매핑 | ✅ |
|
||||
| [docs/handlers.md](docs/handlers.md) | 템플릿 전용 핸들러와 부트스트랩 | ✅ |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | ✅ |
|
||||
<!-- @generated:docs-index END -->
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
<!-- @intent START -->
|
||||
| 증상 | 원인 | 조치 |
|
||||
|---|---|---|
|
||||
| 사이드바 메뉴 아이콘이 안 보임 | `Icon` 컴포넌트로 렌더 시도(API 는 FontAwesome 클래스 문자열을 내려줌) | `I` 컴포넌트로 교체 (`docs/components.md` "AdminSidebar 상세" 참고) |
|
||||
| 다른 admin 템플릿으로 교체 후 특정 확장 화면이 깨짐 | 그 확장이 필수 컴포넌트 목록 밖의 컴포넌트를 사용 | 그 확장의 레이아웃을 필수 컴포넌트(35개)만으로 재작성하거나, 새 템플릿에 같은 컴포넌트를 구현 |
|
||||
| 사이드바 접힘 상태가 새로고침 후 풀림 | localStorage 접근 실패(시크릿 모드 등) 또는 부트스트랩 순서 문제 | `initSidebar()` 가 템플릿 부트스트랩에서 호출되는지 확인 (`src/index.ts`) |
|
||||
| 로그인 화면에 다크 모드/언어 전환이 적용 안 됨 | 로그인 화면은 `_admin_base` 를 상속하지 않아 초기화 경로가 다름 | `admin_login.json` 자체의 초기화 액션을 확인 — `_admin_base` 수정으로는 반영되지 않는다 |
|
||||
<!-- @intent END -->
|
||||
|
||||
## 변경 이력
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## 라이선스
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,20 @@
|
||||
# Admin Basic 개발자 문서
|
||||
|
||||
> templates/_bundled/sirsoft-admin_basic · 템플릿
|
||||
|
||||
<!-- @generated:stats START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
**훅 수**: 0 · **구독 훅 수**: 0 · **라우트 수**: 29 · **모델 수**: 0 · **테이블 수**: 0 · **마이그레이션 수**: 0 · **레이아웃 수**: 145 · **핸들러 수**: 17
|
||||
<!-- @generated:stats END -->
|
||||
|
||||
## 문서 목차
|
||||
|
||||
<!-- @generated:doc-toc START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [architecture.md](architecture.md) | 설계 의도·계층 지도·디렉토리 맵 |
|
||||
| [components.md](components.md) | 템플릿이 제공하는 컴포넌트 |
|
||||
| [layouts.md](layouts.md) | 레이아웃 목록과 라우트 매핑 |
|
||||
| [handlers.md](handlers.md) | 템플릿 전용 핸들러와 부트스트랩 |
|
||||
| [../AGENTS.md](../AGENTS.md) | 에이전트·확장개발자 진입점 |
|
||||
| [../README.md](../README.md) | 사람(도입검토자·운영자) 진입점 |
|
||||
<!-- @generated:doc-toc END -->
|
||||
@@ -0,0 +1,60 @@
|
||||
# Admin Basic — 아키텍처
|
||||
|
||||
> 설계 의도와 계층 구조 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 설계 의도
|
||||
|
||||
<!-- @intent START -->
|
||||
이 템플릿의 설계는 "관리자 화면 디자인"과 "관리자 화면 구성"을 분리하는 데 집중합니다.
|
||||
컴포넌트(디자인 구현)는 이 템플릿이 소유하지만, 어떤 화면을 어떻게 구성할지(레이아웃 JSON)는
|
||||
코어와 모든 번들 확장이 **각자 소유**합니다 — 이 템플릿은 그 구성을 그릴 부품만 제공합니다.
|
||||
그래서 이 템플릿의 실질 소스는 컴포넌트(`src/components/`)와 코어 화면 레이아웃뿐이고, 확장
|
||||
관리자 화면(모듈/플러그인이 소유)은 이 템플릿 디렉토리 밖(`modules/`, `plugins/`)에 있습니다.
|
||||
|
||||
필수 컴포넌트 계약(§AGENTS.md)이 이 분리를 지탱합니다 — 계약이 없으면 확장 개발자가 이
|
||||
템플릿에만 있는 컴포넌트를 무심코 써버려 "관리자 화면 디자인 교체"가 사실상 불가능해집니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 계층 지도
|
||||
|
||||
<!-- @intent START -->
|
||||
```
|
||||
routes.json (URL → 레이아웃 이름 매핑)
|
||||
│
|
||||
▼
|
||||
layouts/*.json (extends: _admin_base)
|
||||
│
|
||||
▼
|
||||
_admin_base.json (사이드바·헤더·Toast·PageTransitionIndicator·콘텐츠 슬롯·푸터)
|
||||
│
|
||||
▼
|
||||
src/components/{basic,composite,layout}/*.tsx (필수 컴포넌트 35개 포함 125개)
|
||||
│
|
||||
▼
|
||||
src/handlers/*.ts (이 템플릿 전용 핸들러 17개 — 범용 핸들러는 코어 ActionDispatcher)
|
||||
```
|
||||
|
||||
이 트리는 코어 화면에만 적용되는 것이 아닙니다 — 모듈/플러그인의 관리자 레이아웃도 같은
|
||||
`_admin_base` → 컴포넌트 경로를 그대로 타므로, 이 템플릿을 고치면 그 확장들의 화면도 같은
|
||||
방향으로 바뀝니다(§AGENTS.md "수정 시 동반 의무" 참고).
|
||||
<!-- @intent END -->
|
||||
|
||||
## 디렉토리
|
||||
|
||||
<!-- @generated:directory-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 역할 | 수정 시 필요한 절차 |
|
||||
|---|---|---|
|
||||
| `template.json` | manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json 동기화 |
|
||||
| `routes.json` | 라우트 → 레이아웃 매핑 | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `layouts/` | 레이아웃 JSON | `php artisan template:update sirsoft-admin_basic --force` (빌드 불필요) |
|
||||
| `src/components/` | React 컴포넌트 | `php artisan template:build` → `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `src/handlers/` | 템플릿 전용 액션 핸들러 | `php artisan template:build` → `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `dist/` | 커밋되는 빌드 산출물 | `--production` 으로 재빌드 (sourceMappingURL 잔존 금지) |
|
||||
| `editor-spec.json` | 레이아웃 편집기 스펙 | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `editor-spec/` | 분할 편집기 스펙 | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `tests/` | 테스트 | 변경 범위만 필터 실행 |
|
||||
| `CHANGELOG.md` | 변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
|
||||
| `components.json` | 편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | `php artisan template:update sirsoft-admin_basic --force` |
|
||||
| `docs/` | 개발자 문서 | 표면 변경 시 `php artisan ext:docgen` 재실행 |
|
||||
| `lang/` | 다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
|
||||
<!-- @generated:directory-map END -->
|
||||
@@ -0,0 +1,587 @@
|
||||
# Admin Basic — 컴포넌트
|
||||
|
||||
> 템플릿이 제공하는 컴포넌트 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 제공 컴포넌트
|
||||
|
||||
<!-- @generated:components START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
컴포넌트 125개 (루트: `src/components`).
|
||||
|
||||
| 분류 | 개수 |
|
||||
|---|---|
|
||||
| `basic` | 39개 |
|
||||
| `composite` | 80개 |
|
||||
| `layout` | 5개 |
|
||||
| `modals` | 1개 |
|
||||
<!-- @generated:components END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
세 분류(basic/composite/layout)는 "얼마나 많이 조합됐는가"로 나뉩니다 — basic 은 HTML 태그를
|
||||
그대로 래핑(`Div`→`<div>`)한 최소 단위, composite 는 basic 을 여러 개 조합해 UI 패턴을 캡슐화한
|
||||
것(`DataGrid`가 `Table`+`Pagination`+정렬 로직을 감싸는 식), layout 은 페이지 구조 자체(Grid/
|
||||
Flex/Container)를 정의합니다. 레이아웃 JSON 저작자는 이 구분을 몰라도 되지만("HTML 태그
|
||||
직접 사용 금지" 규정만 지키면 됨), 컴포넌트를 새로 추가하는 쪽은 이 구분에 맞는 디렉토리
|
||||
(`src/components/{basic,composite,layout}/`)에 넣어야 `components.json` 카탈로그와
|
||||
`editor-spec.json` 팔레트 분류가 어긋나지 않습니다.
|
||||
|
||||
위 개수는 코드에서 실측되므로 시간이 지나면 달라집니다 — 이 문서에 구체적인 개수를 하드코딩해
|
||||
적지 않습니다(과거 버전 문서가 37/66/8 로 적었다가 실제로는 39/80/5+modals 1 이 되어 있었던
|
||||
사례가 있습니다). 정확한 전체 목록·Props 는 [component-props.md](../../../../docs/frontend/component-props.md)
|
||||
· [component-props-composite.md](../../../../docs/frontend/component-props-composite.md) 를
|
||||
따라갑니다 — 이 문서는 "이 템플릿에서만" 유효한 계약(필수 컴포넌트·AdminSidebar·SlotContainer)
|
||||
만 다룹니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 필수 컴포넌트 (모듈 호환성)
|
||||
|
||||
모듈 개발자가 아래 컴포넌트만 사용하면 **다른 Admin 템플릿으로 교체해도 화면이 깨지지
|
||||
않습니다**. 목록의 SSoT 는 `config/template.php` 의 `required_admin_components` 이며, 아래
|
||||
표는 그 값을 그대로 옮긴 것입니다(코드가 SSoT — 표와 설정이 어긋나면 설정이 맞습니다).
|
||||
|
||||
| 분류 | 컴포넌트 |
|
||||
|---|---|
|
||||
| Basic (27개) | `A`, `Button`, `Checkbox`, `Div`, `Form`, `H1`, `H2`, `H3`, `Icon`, `Img`, `Input`, `Label`, `Li`, `Nav`, `P`, `Section`, `Select`, `Span`, `Svg`, `Table`, `Tbody`, `Td`, `Textarea`, `Th`, `Thead`, `Tr`, `Ul` |
|
||||
| Composite (8개) | `Alert`, `Badge`, `Card`, `DataTable`, `FormField`, `Modal`, `PageHeader`, `Pagination` |
|
||||
|
||||
```text
|
||||
✅ 필수 컴포넌트 목록에 있는 것만 사용 (다른 Admin 템플릿 호환 보장)
|
||||
✅ 템플릿의 베이스 레이아웃(`_admin_base`)을 extends
|
||||
❌ 이 템플릿에만 있는 커스텀 컴포넌트 사용 금지
|
||||
```
|
||||
|
||||
기본은 `validate_on_install: false` — 설치 시 필수 컴포넌트 검증은 기본적으로 수행되지 않고
|
||||
경고에 그칩니다(`block_on_failure: false`). 검증을 강제하려면 두 설정을 함께 켭니다.
|
||||
|
||||
## AdminSidebar 상세
|
||||
|
||||
계층형 관리자 메뉴를 그리는 컴포넌트로, 데이터 소스로 받은 메뉴 트리를 그대로 넘기면 됩니다.
|
||||
|
||||
```typescript
|
||||
interface MenuItem {
|
||||
id: string | number;
|
||||
name: string | { ko: string; en: string };
|
||||
slug: string;
|
||||
url?: string | null;
|
||||
icon?: string; // FontAwesome 클래스 (예: "fas fa-home")
|
||||
children?: MenuItem[];
|
||||
is_active?: boolean;
|
||||
module_id?: number | null; // 모듈이 등록한 메뉴인지 식별
|
||||
}
|
||||
|
||||
interface AdminSidebarProps {
|
||||
logo?: string;
|
||||
logoAlt?: string;
|
||||
menu: MenuItem[]; // 필수
|
||||
collapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
className?: string;
|
||||
currentLocale?: string; // 미지정 시 G7Core.locale.current() 자동 사용
|
||||
id?: string; // 레이아웃 편집기 코어 일괄 ID
|
||||
}
|
||||
```
|
||||
|
||||
아이콘은 `Icon` 컴포넌트(IconName enum)가 아니라 `I` 컴포넌트 + FontAwesome 클래스 문자열로
|
||||
렌더링됩니다 — 메뉴 API 가 `icon` 필드를 이미 `"fas fa-home"` 형태 클래스 문자열로 내려주기
|
||||
때문입니다. 새 메뉴 아이콘을 추가할 때 `Icon name="home"` 식으로 쓰면 렌더되지 않습니다.
|
||||
|
||||
## SlotContainer 상세
|
||||
|
||||
동적 슬롯 렌더링 컨테이너입니다 — 다른 컴포넌트가 `slot` prop 으로 자신이 속할 슬롯 ID 를
|
||||
표현식으로 지정하면(예: `"{{_local.isVisible ? 'basic_filters' : 'detail_filters'}}"`), 그
|
||||
표현식이 상태 변화에 따라 재평가되어 컴포넌트가 슬롯 사이를 동적으로 이동합니다.
|
||||
|
||||
```typescript
|
||||
interface SlotContainerProps {
|
||||
slotId: string; // 필수 — 렌더링할 슬롯 ID
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{ "id": "category_filter", "type": "basic", "name": "Div", "slot": "{{_local.isVisible ? 'basic_filters' : 'detail_filters'}}", "slotOrder": 1, "children": [] }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "id": "basic_filters_container", "type": "composite", "name": "SlotContainer", "props": { "slotId": "basic_filters" } }
|
||||
```
|
||||
|
||||
## 이관 원문 상세
|
||||
|
||||
> 아래는 코어 `docs/frontend/templates/sirsoft-admin_basic/components.md` 에 있던 원문을
|
||||
> 이 문서로 옮긴 것입니다(#601). 이관 시점 그대로 보존하되, **코드가 SSoT 인 값과 어긋나는
|
||||
> 부분에는 정정 주석**을 달았습니다 — 실측 총계는 위 「제공 컴포넌트」 블록이, 필수 컴포넌트
|
||||
> 목록은 `config/template.php` 의 `required_admin_components` 가 SSoT 입니다.
|
||||
|
||||
### 컴포넌트 개요
|
||||
|
||||
> **정정(#601)**: 아래 개수는 이관 시점(2026-03) 문서 값입니다. 코드 실측은 basic 39 · composite 80 ·
|
||||
> layout 5 · modals 1 = **125종**이며 위 「제공 컴포넌트」 블록이 SSoT 입니다. 아래 목록에 없는
|
||||
> 컴포넌트가 있을 수 있습니다.
|
||||
|
||||
| 타입 | 개수 | 설명 |
|
||||
|------|------|------|
|
||||
| Basic | 37 | HTML 태그 래핑 — 최소 단위 컴포넌트 |
|
||||
| Composite | 66 | 기본 컴포넌트 조합 — UI 패턴 캡슐화 |
|
||||
| Layout | 8 | 페이지 구조 정의 — 컨테이너/그리드/플렉스 |
|
||||
| **합계** | **111** | |
|
||||
|
||||
**소스**: `templates/_bundled/sirsoft-admin_basic/components.json`
|
||||
**컴포넌트 소스**: `templates/_bundled/sirsoft-admin_basic/src/components/{basic,composite,layout}/*.tsx`
|
||||
|
||||
---
|
||||
|
||||
### Basic Components (37개)
|
||||
|
||||
> **정정(#601)**: 제목의 개수는 이관 시점 문서 값입니다(코드 실측 39종). 목록 자체는 원문 그대로입니다.
|
||||
|
||||
HTML 태그를 래핑하는 최소 단위 컴포넌트입니다. 모든 Basic 컴포넌트는 `className`, `children` props를 공통으로 지원합니다.
|
||||
|
||||
#### 텍스트/링크
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `A` | HTML anchor element wrapper | href, target | - |
|
||||
| `H1` | HTML h1 heading element wrapper | - | - |
|
||||
| `H2` | HTML h2 heading element wrapper | - | - |
|
||||
| `H3` | HTML h3 heading element wrapper | - | - |
|
||||
| `H4` | HTML h4 heading element wrapper | - | - |
|
||||
| `P` | HTML paragraph element wrapper | - | - |
|
||||
| `Span` | HTML span element wrapper | - | - |
|
||||
| `Label` | HTML label element wrapper | - | - |
|
||||
| `Pre` | 서식 유지 텍스트 래퍼 | - | - |
|
||||
| `Code` | 인라인 코드 래퍼 | - | - |
|
||||
|
||||
#### 컨테이너
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `Div` | 범용 컨테이너 | - | - |
|
||||
| `Section` | 시맨틱 섹션 | - | - |
|
||||
| `Nav` | 네비게이션 래퍼 | - | - |
|
||||
| `Form` | 폼 컨테이너 (자동 바인딩 지원) | - | - |
|
||||
| `Fragment` | React.Fragment — iterator에서 DOM 래퍼 없이 사용 | - | - |
|
||||
|
||||
#### 폼 입력
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props | 바인딩 |
|
||||
|----------|------|-----------|--------|
|
||||
| `Input` | 텍스트 입력 | label, error | checkable |
|
||||
| `Select` | 선택 박스 | label, error | - |
|
||||
| `Option` | Select 내부 옵션 | value | - |
|
||||
| `Optgroup` | Select 옵션 그룹화 | label | - |
|
||||
| `Textarea` | 텍스트 영역 | label, error | - |
|
||||
| `Checkbox` | 체크박스 | label | checked |
|
||||
| `FileInput` | 파일 입력 (검증 포함) | accept, maxSize, onChange, onError, buttonText, placeholder, disabled | - |
|
||||
| `Button` | 버튼 | variant (`primary`\|`secondary`\|`danger`\|`success`), size (`sm`\|`md`\|`lg`) | - |
|
||||
|
||||
#### 테이블
|
||||
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|------|
|
||||
| `Table` | HTML table wrapper |
|
||||
| `Thead` | 테이블 헤더 그룹 |
|
||||
| `Tbody` | 테이블 본문 그룹 |
|
||||
| `Tfoot` | 테이블 푸터 그룹 |
|
||||
| `Tr` | 테이블 행 |
|
||||
| `Th` | 테이블 헤더 셀 |
|
||||
| `Td` | 테이블 데이터 셀 |
|
||||
|
||||
#### 리스트
|
||||
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|------|
|
||||
| `Ul` | 순서 없는 리스트 |
|
||||
| `Ol` | 순서 있는 리스트 |
|
||||
| `Li` | 리스트 아이템 |
|
||||
|
||||
#### 미디어/아이콘
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `Img` | 이미지 | src, alt |
|
||||
| `Icon` | FontAwesome 아이콘 | name, iconStyle, size, color, spin, pulse, fixedWidth, ariaLabel |
|
||||
| `Svg` | SVG 컨테이너 | - |
|
||||
| `I` | HTML i 태그 (FontAwesome 클래스 직접 사용) | style |
|
||||
|
||||
---
|
||||
|
||||
### Composite Components (66개)
|
||||
|
||||
> **정정(#601)**: 제목의 개수는 이관 시점 문서 값입니다(코드 실측 80종). 목록 자체는 원문 그대로입니다.
|
||||
|
||||
기본 컴포넌트를 조합하여 UI 패턴을 캡슐화한 복합 컴포넌트입니다.
|
||||
|
||||
#### 데이터 표시
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `DataGrid` | 정렬/필터링/페이지네이션 데이터 그리드 | columns, data, sortable, pagination, pageSize, ... |
|
||||
| `CardGrid` | 카드 그리드 레이아웃 (스켈레톤 로딩, 페이지네이션) | data, cardColumns, columns, gap, responsiveColumns, ... |
|
||||
| `Pagination` | 페이지네이션 | currentPage, totalPages, onPageChange, maxVisiblePages, showFirstLast, ... |
|
||||
| `Badge` | 색상 기반 라벨 뱃지 | color, text, size, style |
|
||||
| `StatusBadge` | 상태 뱃지 (아이콘 포함) | status, label, showIcon, iconName, style |
|
||||
| `StatCard` | 통계 카드 (값, 라벨, 추이 표시) | value, label, change, changeLabel, iconName, ... |
|
||||
| `EmptyState` | 데이터 없음 상태 표시 | title, description, iconName, illustrationSrc, ... |
|
||||
| `HtmlContent` | HTML 안전 렌더링 (DOMPurify XSS 방지) | content, isHtml, purifyConfig, text |
|
||||
|
||||
#### 폼/입력
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `FormField` | 폼 필드 래퍼 (라벨, 에러, 헬퍼 텍스트) | label, required, error, helperText, labelClassName, ... |
|
||||
| `Toggle` | 토글 스위치 (Flowbite 스타일) | checked, value, onChange, disabled, label, ... |
|
||||
| `TagInput` | 태그 입력 (다중 선택, 생성 가능) | value, options, onChange, creatable, placeholder, ... |
|
||||
| `TagSelect` | 태그 기반 선택 표시 | options, value, onChange, placeholder, disabled |
|
||||
| `RadioGroup` | 라디오 버튼 그룹 | name, value, options, onChange, disabled, ... |
|
||||
| `SearchBar` | 검색 바 (자동완성 제안) | placeholder, value, onChange, onSearch, suggestions, ... |
|
||||
| `SearchableDropdown` | 검색 가능 드롭다운 (단일/다중) | options, value, onChange, multiple, searchPlaceholder, ... |
|
||||
| `ChipCheckbox` | 칩 스타일 체크박스 (필터 UI) | value, checked, icon, label, style, ... |
|
||||
| `MultilingualInput` | 탭 방식 다국어 텍스트 입력 | value, onChange, inputType, availableLocales, defaultLocale, ... |
|
||||
| `MultilingualTagInput` | 다국어 태그 입력 (모달 편집) | value, onChange, placeholder, disabled, creatable, ... |
|
||||
| `MultilingualTabPanel` | 다국어 탭 패널 (로케일 제어) | style, variant, defaultLocale, onLocaleChange |
|
||||
| `DynamicFieldList` | 동적 필드 목록 (드래그 정렬, 추가/삭제) | items, columns, onChange, onAddItem, onRemoveItem, ... |
|
||||
| `FileUploader` | 파일 업로드 (드래그앤드롭, 이미지 압축) | attachmentableType, attachmentableId, collection, maxFiles, maxSize, ... |
|
||||
| `IconSelect` | 아이콘 선택 드롭다운 | value, onChange, options, placeholder, searchPlaceholder, ... |
|
||||
|
||||
#### 에디터
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `HtmlEditor` | HTML/텍스트 편집기 (편집/미리보기 토글) | content, onChange, isHtml, onHtmlModeChange, previewMode, ... |
|
||||
| `CodeEditor` | JSON 코드 편집기 (Monaco Editor) | value, onChange, language, height, readOnly, ... |
|
||||
|
||||
#### 네비게이션
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `TabNavigation` | 탭 네비게이션 | tabs, activeTabId, onTabChange, variant, style |
|
||||
| `TabNavigationScroll` | 탭 네비게이션 + 스크롤 (setState + scrollToSection 내장) | tabs, activeTabId, actions, style, activeClassName, ... |
|
||||
| `Breadcrumb` | 브레드크럼 | items, separator, showHome, homeHref, maxItems |
|
||||
| `ActionMenu` | 드롭다운 액션 메뉴 | items, triggerLabel, triggerIconName, position, style |
|
||||
| `Dropdown` | 드롭다운 메뉴 | label, items, onItemClick, position, style |
|
||||
|
||||
#### 모달/다이얼로그
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `Modal` | 모달 다이얼로그 (오버레이, 포커스 트랩) | isOpen, onClose, title, width, style |
|
||||
| `Dialog` | 다이얼로그 (Modal 별칭) | isOpen, onClose, title, content, actions, ... |
|
||||
| `ConfirmDialog` | 확인/취소 다이얼로그 | isOpen, onClose, title, message, confirmText, ... |
|
||||
| `AlertDialog` | 알림 다이얼로그 (확인 버튼만) | isOpen, onClose, title, message, confirmText, ... |
|
||||
| `Toast` | 토스트 알림 | toasts, position, onRemove |
|
||||
| `Alert` | 알림 메시지 | type, message, dismissible, onDismiss |
|
||||
|
||||
#### 관리자 UI
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `AdminSidebar` | 관리자 사이드바 (계층형 메뉴) | logo, logoAlt, menu, collapsed, onToggleCollapse |
|
||||
| `AdminHeader` | 관리자 헤더 | user, notifications, onNotificationClick, onProfileClick, onLogoutClick |
|
||||
| `AdminFooter` | 관리자 푸터 | copyright, version, quickLinks |
|
||||
| `PageHeader` | 페이지 헤더 (제목, 브레드크럼, 액션) | title, subtitle, breadcrumbItems, tabs, onTabChange, ... |
|
||||
| `LoginForm` | 로그인 폼 (이메일/비밀번호 검증) | submitButtonText, emailPlaceholder, passwordPlaceholder, forgotPasswordText, forgotPasswordUrl, ... |
|
||||
| `UserProfile` | 사용자 프로필 드롭다운 | user, profileText, logoutText, onProfileClick, onLogoutClick |
|
||||
| `NotificationCenter` | 알림 센터 | notifications, titleText, emptyText, onNotificationClick |
|
||||
| `ThemeToggle` | 테마 모드 전환 (다크/라이트/자동) | onThemeChange, autoText, lightText, darkText |
|
||||
| `LanguageSelector` | 언어 선택 드롭다운 | availableLocales, languageText, apiEndpoint, onLanguageChange, inline |
|
||||
| `PageTransitionIndicator` | 페이지 전환 로딩 표시 | style |
|
||||
|
||||
#### 레이아웃 편집
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `LayoutEditorHeader` | 레이아웃 편집기 헤더 | layoutName, onBack, onPreview, onSave, isSaving |
|
||||
| `LayoutFileList` | 레이아웃 파일 목록 | files, selectedId, onSelect |
|
||||
| `LayoutHistoryPanel` | 레이아웃 히스토리 패널 | layoutId, versions, onRestore |
|
||||
| `LayoutWarnings` | 레이아웃 경고 표시 | warnings |
|
||||
| `VersionList` | 버전 목록 아이템 | versions, selectedId, onSelect |
|
||||
|
||||
#### 확장 관리
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `TemplateCard` | 템플릿 카드 (설치/활성화) | image, imageAlt, vendor, name, version, ... |
|
||||
| `ExtensionBadge` | 확장 섹션 뱃지 (identifier로 이름 자동 조회) | type, identifier, name, installedModules, installedPlugins, ... |
|
||||
| `ProductCard` | 상품 카드 (이미지, 제목, 가격, 액션) | imageUrl, imageAlt, title, subtitle, description, ... |
|
||||
|
||||
#### 고급 기능
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `SlotContainer` | 동적 슬롯 렌더링 컨테이너 | slotId, emptyContent, style, id |
|
||||
| `FilterGroup` | 다중 필터 그룹 | title, filters, onChange, onReset, showResetButton, ... |
|
||||
| `FilterVisibilitySelector` | 필터 가시성 상태 관리 (UI 없음, localStorage 저장) | id, visibleFilters, defaultFilters, onFilterVisibilityChange |
|
||||
| `ColumnSelector` | 테이블 컬럼 표시/숨김 선택 드롭다운 | columns, visibleColumns, onColumnVisibilityChange, triggerLabel, triggerIconName, ... |
|
||||
| `PermissionTree` | 계층형 권한 트리 (체크박스 선택) | data, value, onChange, disabled, desktopColumns |
|
||||
| `CategoryTree` | 계층형 카테고리 트리 (체크박스 선택) | data, expandedIds, selectedIds, searchKeyword, showProductCount, ... |
|
||||
| `SortableMenuList` | 드래그앤드롭 계층형 메뉴 목록 | items, selectedId, onSelect, onOrderChange, onToggleStatus, ... |
|
||||
| `SortableMenuItem` | 개별 드래그 가능 메뉴 아이템 | item, isSelected, isExpanded, level, onClick, ... |
|
||||
| `IconButton` | 아이콘 버튼 | iconName, label, onClick, variant, size, ... |
|
||||
| `Accordion` | 아코디언 (접기/펼치기) | defaultOpen, isOpen, onToggle, style, disabled |
|
||||
| `Card` | 카드 컨테이너 (헤더, 본문, 푸터) | title, content, imageUrl, imageAlt, onClick, ... |
|
||||
| `LoadingSpinner` | 로딩 스피너 | size, color, fullscreen, text |
|
||||
| `ImageGallery` | 라이트박스 이미지 갤러리 (줌, 다운로드, 썸네일) | images, initialIndex, onClose |
|
||||
|
||||
---
|
||||
|
||||
### Layout Components (8개)
|
||||
|
||||
> **정정(#601)**: 제목의 개수는 이관 시점 문서 값입니다(코드 실측 5종). 목록 자체는 원문 그대로입니다.
|
||||
|
||||
페이지 구조를 정의하는 레이아웃 컴포넌트입니다.
|
||||
|
||||
| 컴포넌트 | 설명 | 주요 Props |
|
||||
|----------|------|-----------|
|
||||
| `Container` | Flex/Grid 컨테이너 | mode, direction, justify, align, wrap, gap, cols, responsive, padding, maxWidth, centered, style |
|
||||
| `Grid` | CSS Grid 반응형 그리드 | cols, responsive, gap, rowGap, colGap, autoRows, autoCols, flow, style |
|
||||
| `Flex` | Flexbox 레이아웃 | direction, justify, align, wrap, gap, grow, shrink, style |
|
||||
| `SectionLayout` | 섹션 레이아웃 (스타일 옵션) | title, subtitle, padding, background, maxWidth, centered, border, shadow, rounded, style |
|
||||
| `ThreeColumnLayout` | 3열 레이아웃 (좌/중/우 슬롯) | leftWidth, rightWidth, leftSlot, centerSlot, rightSlot, style |
|
||||
| `RichSelect` | 커스텀 항목 렌더링 셀렉트 | options, value, onChange, placeholder, disabled, maxHeight, selectedChildren |
|
||||
| `DropdownButton` | 포탈 기반 드롭다운 버튼 | label, icon, iconPosition, position |
|
||||
| `DropdownMenuItem` | DropdownButton 내부 메뉴 아이템 | label, icon, variant, disabled, divider |
|
||||
|
||||
---
|
||||
|
||||
### 필수 컴포넌트 (모듈 호환성)
|
||||
|
||||
모듈 개발자가 이 컴포넌트들만 사용하면 **모든 Admin 템플릿에서 동작이 보장**됩니다.
|
||||
|
||||
#### 필수 Basic (27개)
|
||||
|
||||
| 카테고리 | 컴포넌트 |
|
||||
|----------|----------|
|
||||
| 텍스트/링크 | `A`, `H1`, `H2`, `H3`, `P`, `Span`, `Label` |
|
||||
| 컨테이너 | `Div`, `Section`, `Nav` |
|
||||
| 폼 | `Form`, `Input`, `Select`, `Checkbox`, `Textarea`, `Button` |
|
||||
| 테이블 | `Table`, `Thead`, `Tbody`, `Tr`, `Th`, `Td` |
|
||||
| 리스트 | `Ul`, `Li` |
|
||||
| 미디어 | `Icon`, `Img`, `Svg` |
|
||||
|
||||
#### 필수 Composite (15개)
|
||||
|
||||
> **정정(#601)**: 아래 표는 `config/template.php` 의 `required_admin_components` 와 일치하지 않습니다 —
|
||||
> 실제 필수 Composite 는 위 「필수 컴포넌트 (모듈 호환성)」 절의 8종(`Alert` `Badge` `Card`
|
||||
> `DataTable` `FormField` `Modal` `PageHeader` `Pagination`)이며 설정 파일이 SSoT 입니다.
|
||||
> 아래 목록은 이관 시점 문서를 보존한 것이므로 필수 여부의 근거로 쓰지 않습니다.
|
||||
|
||||
| 카테고리 | 컴포넌트 | 설명 |
|
||||
|----------|----------|------|
|
||||
| 데이터 표시 | `DataGrid` | 목록 페이지 필수 (테이블 형식) |
|
||||
| | `CardGrid` | 목록 페이지 필수 (카드 형식) |
|
||||
| | `Pagination` | 페이지네이션 |
|
||||
| | `Badge` | 상태 표시 |
|
||||
| 폼 | `FormField` | 폼 필드 래퍼 |
|
||||
| | `Toggle` | 토글 스위치 |
|
||||
| | `TagInput` | 태그 입력 |
|
||||
| 에디터 | `HtmlEditor` | HTML/텍스트 에디터 |
|
||||
| | `CodeEditor` | 코드 에디터 (Monaco) |
|
||||
| 피드백 | `Modal` | 모달 다이얼로그 |
|
||||
| | `Alert` | 알림 메시지 |
|
||||
| 레이아웃 | `PageHeader` | 페이지 헤더 |
|
||||
| | `Card` | 카드 컨테이너 |
|
||||
| | `AdminSidebar` | 관리자 사이드바 |
|
||||
| | `SlotContainer` | 동적 슬롯 렌더링 |
|
||||
|
||||
#### 설정 파일
|
||||
|
||||
> **정정(#601)**: 아래 PHP 스니펫은 이관 시점 값입니다. 현재 `config/template.php` 의 실제 배열은 위
|
||||
> 「필수 컴포넌트 (모듈 호환성)」 절의 표(Basic 27 + Composite 8)와 일치합니다.
|
||||
|
||||
필수 컴포넌트 목록은 `config/template.php`에 정의:
|
||||
|
||||
```php
|
||||
'required_admin_components' => [
|
||||
'DataTable', 'Pagination', 'Badge',
|
||||
'Form', 'FormField', 'Input', 'Select', 'Checkbox',
|
||||
'Button', 'Modal', 'Alert',
|
||||
'PageHeader', 'Card',
|
||||
],
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 모듈 개발자 가이드
|
||||
|
||||
#### 핵심 원칙
|
||||
|
||||
```text
|
||||
✅ 필수 컴포넌트 목록에 있는 컴포넌트만 사용
|
||||
✅ 템플릿의 베이스 레이아웃 (_admin_base)을 extends
|
||||
❌ 특정 템플릿에만 존재하는 커스텀 컴포넌트 사용 금지
|
||||
```
|
||||
|
||||
#### 레이아웃 작성 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"layout_name": "sirsoft-ecommerce_admin_products_index",
|
||||
"extends": "_admin_base",
|
||||
"slots": {
|
||||
"content": [
|
||||
{
|
||||
"id": "products-table",
|
||||
"type": "composite",
|
||||
"name": "DataGrid",
|
||||
"props": {
|
||||
"columns": [],
|
||||
"data": "{{products?.data?.data}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 템플릿 개발자 가이드
|
||||
|
||||
Admin 타입 템플릿 개발 시 필수 컴포넌트를 반드시 구현해야 합니다.
|
||||
|
||||
#### 구현 체크리스트
|
||||
|
||||
```text
|
||||
□ DataGrid - 정렬, 필터링, 페이지네이션 지원
|
||||
□ Pagination - 페이지 이동, 페이지 크기 변경
|
||||
□ Badge - 다양한 상태 색상 지원
|
||||
□ Form - 유효성 검사, 제출 처리
|
||||
□ FormField - 라벨, 에러 메시지, 필수 표시
|
||||
□ Input - 텍스트, 이메일, 비밀번호 등 타입 지원
|
||||
□ Select - 단일/다중 선택, 검색 기능
|
||||
□ Checkbox - 단일/그룹 체크박스
|
||||
□ Button - 다양한 variant와 size
|
||||
□ Modal - 열기/닫기, 확인/취소 액션
|
||||
□ Alert - success, warning, error, info 타입
|
||||
□ PageHeader - 제목, 브레드크럼, 액션 버튼
|
||||
□ Card - 헤더, 본문, 푸터 영역
|
||||
□ AdminSidebar - 계층형 메뉴, 다국어 지원
|
||||
```
|
||||
|
||||
#### Props 인터페이스 일관성
|
||||
|
||||
모든 Admin 템플릿은 동일한 Props 인터페이스를 구현해야 합니다. 상세 Props는 다음 문서를 참조:
|
||||
|
||||
- [컴포넌트 Props 레퍼런스 (Basic)](../../../../docs/frontend/component-props.md)
|
||||
- [컴포넌트 Props 레퍼런스 (Composite)](../../../../docs/frontend/component-props-composite.md)
|
||||
|
||||
---
|
||||
|
||||
### AdminSidebar 상세
|
||||
|
||||
#### MenuItem 인터페이스
|
||||
|
||||
```typescript
|
||||
interface MenuItem {
|
||||
id: string | number;
|
||||
name: string | { ko: string; en: string };
|
||||
slug: string;
|
||||
url?: string | null;
|
||||
icon?: string; // FontAwesome 클래스 (예: "fas fa-home")
|
||||
children?: MenuItem[];
|
||||
is_active?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### AdminSidebarProps
|
||||
|
||||
```typescript
|
||||
interface AdminSidebarProps {
|
||||
logo?: string;
|
||||
logoAlt?: string;
|
||||
menu: MenuItem[]; // 필수
|
||||
collapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
className?: string;
|
||||
currentLocale?: string; // 기본값: 'ko'
|
||||
}
|
||||
```
|
||||
|
||||
#### 아이콘 처리
|
||||
|
||||
```text
|
||||
✅ I 컴포넌트 + FontAwesome 클래스 (<I className="fas fa-home w-5 h-5" />)
|
||||
❌ Icon 컴포넌트 + IconName enum (금지 — API가 FontAwesome 클래스 문자열 직접 제공)
|
||||
```
|
||||
|
||||
#### 레이아웃 JSON 사용 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"data_sources": [
|
||||
{
|
||||
"id": "admin_menu",
|
||||
"type": "api",
|
||||
"endpoint": "/api/admin/menus",
|
||||
"method": "GET",
|
||||
"auto_fetch": true,
|
||||
"auth_required": true
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"id": "admin_sidebar",
|
||||
"type": "composite",
|
||||
"name": "AdminSidebar",
|
||||
"props": {
|
||||
"menu": "{{admin_menu.data}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### SlotContainer 상세
|
||||
|
||||
#### SlotContainerProps
|
||||
|
||||
```typescript
|
||||
interface SlotContainerProps {
|
||||
slotId: string; // 필수 — 렌더링할 슬롯 ID
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### 슬롯 시스템 동작
|
||||
|
||||
```text
|
||||
1. slot 속성 컴포넌트 → SlotContext에 등록
|
||||
2. SlotContainer가 해당 slotId의 컴포넌트 렌더링
|
||||
3. 상태 변화 시 slot 표현식 재평가로 동적 이동
|
||||
```
|
||||
|
||||
#### 사용 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "category_filter",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"slot": "{{_local.isVisible ? 'basic_filters' : 'detail_filters'}}",
|
||||
"slotOrder": 1,
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "basic_filters_container",
|
||||
"type": "composite",
|
||||
"name": "SlotContainer",
|
||||
"props": { "slotId": "basic_filters" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 관련 문서
|
||||
|
||||
- [sirsoft-admin_basic 핸들러](handlers.md)
|
||||
- [sirsoft-admin_basic 레이아웃](layouts.md)
|
||||
- [sirsoft-basic 컴포넌트](../../../../docs/frontend/templates/sirsoft-basic/components.md)
|
||||
- [컴포넌트 개발 규칙](../../../../docs/frontend/components.md)
|
||||
- [컴포넌트 Props 레퍼런스](../../../../docs/frontend/component-props.md)
|
||||
- [컴포넌트 Props 레퍼런스 - Composite](../../../../docs/frontend/component-props-composite.md)
|
||||
@@ -0,0 +1,584 @@
|
||||
# Admin Basic — 핸들러
|
||||
|
||||
> 템플릿 전용 핸들러와 부트스트랩 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 템플릿 전용 핸들러
|
||||
|
||||
<!-- @generated:handlers START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
핸들러 17개 (정의: `src/handlers/index.ts`).
|
||||
|
||||
| 핸들러 | 레이아웃에서 부르는 이름 |
|
||||
|---|---|
|
||||
| `detectAssetUrlMode` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `checkAssetUrlModeDrift` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `setTheme` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initTheme` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `scrollToSection` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initMenuFromUrl` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initFilterVisibility` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `saveFilterVisibility` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `toggleFilterVisibility` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `resetFilterVisibility` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `saveMultilingualTag` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `cancelMultilingualTag` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `updateMultilingualTagValue` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `setDateRange` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `toggleSidebar` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `initSidebar` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
| `downloadAttachment` | (템플릿 전용 — 네임스페이스 없음) |
|
||||
<!-- @generated:handlers END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
`setLocale` 이 이 목록에 없는 것이 정상입니다 — 로케일 전환은 엔진 레벨(ActionDispatcher)
|
||||
빌트인으로 승격되어 더 이상 이 템플릿이 등록하지 않습니다(`src/handlers/index.ts` 상단 주석
|
||||
참고). 과거 버전 문서가 `setLocale` 을 이 템플릿의 핸들러로 적었다면 그것은 이관 전 버전
|
||||
기준이므로 따르지 않습니다. 새 핸들러를 추가할 때는 `src/handlers/index.ts` 의 맵 객체 한
|
||||
곳에만 등록하면 자동으로 ActionDispatcher 에 반영됩니다 — 다른 곳에 흩어 등록하지 않습니다.
|
||||
|
||||
이 핸들러들은 **이 템플릿에서만** 등록되므로 다른 템플릿에서는 미지원일 수 있습니다. 범용
|
||||
핸들러(`navigate`/`apiCall`/`setState` 등)는 [actions-handlers.md](../../../../docs/frontend/actions-handlers.md)
|
||||
를 참고하고, 여기서는 이 템플릿 전용 핸들러만 다룹니다.
|
||||
|
||||
### setTheme / initTheme
|
||||
|
||||
다크/라이트/자동(`auto`, 시스템 설정 따름) 테마를 전환·복원합니다. `setTheme` 은 localStorage
|
||||
저장 + `document.documentElement` 클래스 적용(Tailwind `dark:` variant 활성화)을,
|
||||
`initTheme` 은 params 없이 `init_actions` 에서 호출해 저장된 테마를 앱 시작 시 복원합니다.
|
||||
|
||||
```json
|
||||
{ "type": "click", "handler": "setTheme", "params": { "theme": "{{_global.theme === 'dark' ? 'light' : 'dark'}}" } }
|
||||
```
|
||||
|
||||
### scrollToSection
|
||||
|
||||
`params.selector`(CSS 선택자, 필수) 로 지정한 요소로 부드럽게 스크롤합니다. `params.offset`
|
||||
(기본 `0`, 음수면 위로)은 고정 헤더 높이를 보상할 때 씁니다.
|
||||
|
||||
```json
|
||||
{ "type": "click", "handler": "scrollToSection", "params": { "selector": "#features", "offset": -80 } }
|
||||
```
|
||||
|
||||
### initMenuFromUrl
|
||||
|
||||
현재 URL 경로를 사이드바 메뉴 항목과 매칭해 활성 메뉴(및 부모 메뉴의 펼침 상태)를 자동
|
||||
설정합니다. params 없이 `_admin_base.json` 의 `init_actions` 에서 호출합니다.
|
||||
|
||||
### 필터 가시성 핸들러 4종
|
||||
|
||||
목록 화면 필터 패널의 표시/숨김을 localStorage 에 저장해 새로고침 후에도 유지합니다.
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|---|---|---|
|
||||
| `initFilterVisibility` | 없음 | localStorage → `_local` 복원 (`init_actions`에서 호출) |
|
||||
| `saveFilterVisibility` | `{ filters }` | `_local` → localStorage 저장 |
|
||||
| `toggleFilterVisibility` | `{ key }` | 특정 필터 키 가시성 토글 |
|
||||
| `resetFilterVisibility` | 없음 | 전체 초기화 |
|
||||
|
||||
### 다국어 태그 핸들러 3종
|
||||
|
||||
`MultilingualInput` 컴포넌트가 쓰는 태그 편집 핸들러입니다.
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|---|---|---|
|
||||
| `saveMultilingualTag` | `{ field, locale }` | 태그 저장 |
|
||||
| `cancelMultilingualTag` | 없음 | 편집 취소 |
|
||||
| `updateMultilingualTagValue` | `{ field, locale, value }` | 값 업데이트 |
|
||||
|
||||
### setDateRange
|
||||
|
||||
날짜 필터 프리셋 버튼(`today`/`week`/`month`/`3months`/`6months`/`1year`) 클릭 시 시작·종료일을
|
||||
`YYYY-MM-DDTHH:mm:ss` 형식으로 계산해 **반환**합니다(자체적으로 상태를 갱신하지 않음) — 레이아웃
|
||||
JSON 이 `sequence` + `setState` 로 반환값(`$prev.startDate` 등)을 원하는 필드에 반영합니다.
|
||||
|
||||
```json
|
||||
{ "type": "click", "handler": "sequence", "actions": [
|
||||
{ "handler": "setDateRange", "params": { "preset": "today" } },
|
||||
{ "handler": "setState", "params": { "target": "local", "filter.dateFrom": "{{$prev.startDate}}", "filter.dateTo": "{{$prev.endDate}}" } }
|
||||
] }
|
||||
```
|
||||
|
||||
### 사이드바 접힘 핸들러 2종
|
||||
|
||||
데스크톱 좌측 사이드바 접힘 상태를 `localStorage`(`g7_admin_sidebar_collapsed`) 와
|
||||
`_global.sidebarCollapsed` 에 반영합니다. 모바일 슬라이드 사이드바 상태(`_global.sidebarOpen`)와는
|
||||
**독립된 상태**이므로 데스크톱 접힘과 모바일 열림/닫힘을 같은 상태로 착각하지 않습니다.
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|---|---|---|
|
||||
| `initSidebar` | 없음 | 저장된 접힘 상태 복원 (`init_actions`에서 호출) |
|
||||
| `toggleSidebar` | 없음 | 접힘 상태 반전 + 저장 |
|
||||
|
||||
### downloadAttachment
|
||||
|
||||
관리자 화면에서 첨부파일을 `<a href>` 직접 링크가 아니라 `G7Core.api.get(url, {responseType:
|
||||
'blob'})` 로 요청한 뒤 objectURL 로 변환해 다운로드합니다. `<a>` 네비게이션은 Authorization
|
||||
헤더가 실리지 않아 요청이 guest 로 통과하고 활동이력의 행위자(`user_id`)가 비게 되므로, 코어
|
||||
ApiClient 경유로 토큰을 자동 첨부해야 다운로드 행위가 관리자 본인 이력으로 정확히 남습니다.
|
||||
|
||||
```json
|
||||
{ "type": "click", "handler": "downloadAttachment", "params": { "url": "{{attachment.download_url}}", "filename": "{{attachment.original_name}}" } }
|
||||
```
|
||||
|
||||
### 자산 URL 방식 재감지 2종
|
||||
|
||||
관리자 환경설정 화면에서 정적 자산 URL 방식(확장자 있음/없음)을 **브라우저에서** 재감지합니다.
|
||||
서버가 자기 자신에게 curl 하면 nginx vhost·프록시 체인을 우회해 오판할 수 있어, 실제 방문자
|
||||
경로를 재현하려면 브라우저가 프로브를 던져야 합니다.
|
||||
|
||||
| 핸들러 | 호출 시점 | 동작 |
|
||||
|---|---|---|
|
||||
| `detectAssetUrlMode` | 관리자가 "재감지" 버튼 클릭 | 프로브 쌍(확장자 있음/없음)을 던져 판정한 뒤 폼 상태(`general.asset_url_mode`)와 감지 상태 문구를 갱신 — **저장은 하지 않음**(관리자가 결과를 보고 직접 저장) |
|
||||
| `checkAssetUrlModeDrift` | 대시보드 진입 시 자동 | 저장된 설정값과 실제 감지 결과를 대조해 어긋나면 전역 상태(`assetUrlModeDrift`)에 실어 대시보드에 경고 노출 |
|
||||
|
||||
봇은 JavaScript 를 실행하지 않으므로 클라이언트측 자가 복구가 있어도 저장값이 틀린 채로
|
||||
남으면 SEO 렌더링은 계속 깨집니다 — `checkAssetUrlModeDrift` 가 그 간극을 관리자에게 드러내는
|
||||
유일한 지점입니다. 판정 불가(`unavailable`)일 때는 아무 것도 표시하지 않습니다 — 일시적
|
||||
네트워크 장애를 결함 신호로 오인시키지 않기 위함입니다.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 부트스트랩
|
||||
|
||||
<!-- @generated:frontend-entry START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 엔트리 파일 | `src/index.ts` |
|
||||
| 전역 객체 | **미노출** |
|
||||
| 재등록 진입점 | `initTemplate()` |
|
||||
|
||||
재등록 진입점이 전역에 고정 이름으로 노출되지 않으면 로케일 전환 후 이 확장의 액션이 전부 무반응이 됩니다 (오류·토스트 없음).
|
||||
<!-- @generated:frontend-entry END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
전역 객체가 "미노출"인 것은 실수가 아니라 템플릿과 모듈/플러그인의 재등록 규약 차이입니다 —
|
||||
모듈/플러그인은 여러 개가 동시에 활성화되므로 서로를 식별할 `window.__[Name]` 고정 이름이
|
||||
필요하지만, 템플릿은 사이트에 활성 템플릿이 항상 하나뿐이라 그런 식별 필요가 없습니다.
|
||||
`initTemplate()` 을 고칠 때는 핸들러 재등록
|
||||
외의 1회성 부팅 작업(예: 사이드바 초기 상태 복원)을 섞지 않습니다 — 로케일 전환마다 재실행되면
|
||||
안 되는 작업이기 때문입니다(사이드바 복원은 `initSidebar` 를 레이아웃 `init_actions` 에서
|
||||
별도로 호출하는 이유이기도 합니다).
|
||||
<!-- @intent END -->
|
||||
|
||||
## 이관 원문 상세
|
||||
|
||||
> 아래는 코어 `docs/frontend/templates/sirsoft-admin_basic/handlers.md` 에 있던 원문을
|
||||
> 이 문서로 옮긴 것입니다(#601). 핸들러별 params·동작·사용 예시가 여기에 있습니다.
|
||||
> 이관 시점 그대로 보존하되, 현재 코드와 어긋나는 부분에는 정정 주석을 달았습니다 —
|
||||
> 등록 핸들러의 SSoT 는 위 「템플릿 전용 핸들러」 블록입니다.
|
||||
|
||||
### setLocale
|
||||
|
||||
> **정정(#601)**: `setLocale` 은 더 이상 이 템플릿이 등록하는 핸들러가 아닙니다 — 엔진(ActionDispatcher)
|
||||
> 빌트인으로 승격되어 모든 템플릿에서 동작합니다. 아래 서술은 이관 시점 기록이며, 동작·파라미터는
|
||||
> 같지만 **소유 주체가 템플릿이 아니라 엔진**입니다.
|
||||
|
||||
앱 언어를 변경합니다. 번역 파일을 다시 로드하고 UI를 갱신합니다.
|
||||
|
||||
**소스**: `src/handlers/setLocaleHandler.ts`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setLocale",
|
||||
"params": {
|
||||
"locale": "en"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### params
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `locale` | string | ✅ | 변경할 로케일 코드 (예: `"ko"`, `"en"`, `"ja"`) |
|
||||
|
||||
#### 동작
|
||||
|
||||
```text
|
||||
1. 로케일 변경 → 번역 파일 다시 로드
|
||||
2. _global.locale 자동 업데이트
|
||||
3. 모든 $t: 표현식 재평가
|
||||
```
|
||||
|
||||
#### 사용 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "lang_en_btn",
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": {
|
||||
"text": "English"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setLocale",
|
||||
"params": {
|
||||
"locale": "en"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### setTheme / initTheme
|
||||
|
||||
#### setTheme
|
||||
|
||||
다크/라이트 모드를 전환합니다.
|
||||
|
||||
**소스**: `src/handlers/setThemeHandler.ts`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setTheme",
|
||||
"params": {
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### setTheme params
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `theme` | string | ✅ | `"light"`, `"dark"`, `"auto"` (시스템 설정 따름) |
|
||||
|
||||
#### 동작
|
||||
|
||||
```text
|
||||
1. localStorage에 테마 설정 저장
|
||||
2. document.documentElement에 class 적용 (dark/light)
|
||||
3. Tailwind dark: variant 활성화/비활성화
|
||||
```
|
||||
|
||||
#### initTheme
|
||||
|
||||
앱 시작 시 저장된 테마 설정을 적용합니다. 주로 `init_actions`에서 사용합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{
|
||||
"handler": "initTheme"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
params 없이 호출합니다. localStorage에 저장된 테마 설정을 복원합니다.
|
||||
|
||||
#### 사용 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "theme_toggle",
|
||||
"type": "composite",
|
||||
"name": "ThemeToggle",
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setTheme",
|
||||
"params": {
|
||||
"theme": "{{_global.theme === 'dark' ? 'light' : 'dark'}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### scrollToSection
|
||||
|
||||
특정 섹션으로 스크롤합니다. 오프셋 지원에 특화되어 있습니다.
|
||||
|
||||
**소스**: `src/handlers/scrollToSectionHandler.ts`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "scrollToSection",
|
||||
"params": {
|
||||
"selector": "#features",
|
||||
"offset": -80
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### params
|
||||
|
||||
| 필드 | 타입 | 필수 | 기본값 | 설명 |
|
||||
|------|------|------|--------|------|
|
||||
| `selector` | string | ✅ | - | CSS 선택자 (예: `"#section-id"`, `".class-name"`) |
|
||||
| `offset` | number | ❌ | `0` | 스크롤 오프셋 (음수: 위로, 양수: 아래로). 고정 헤더 높이 보상에 사용 |
|
||||
|
||||
#### 동작
|
||||
|
||||
```text
|
||||
1. document.querySelector(selector)로 대상 요소 검색
|
||||
2. 요소의 위치 계산 + offset 적용
|
||||
3. window.scrollTo({ top, behavior: 'smooth' })로 부드러운 스크롤
|
||||
```
|
||||
|
||||
#### 사용 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "nav_features",
|
||||
"type": "basic",
|
||||
"name": "A",
|
||||
"props": {
|
||||
"text": "$t:common.features",
|
||||
"className": "cursor-pointer"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "scrollToSection",
|
||||
"params": {
|
||||
"selector": "#features",
|
||||
"offset": -80
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### initMenuFromUrl
|
||||
|
||||
현재 URL을 기반으로 사이드바/네비게이션 메뉴의 활성 상태를 초기화합니다. 주로 관리자 템플릿의 `init_actions`에서 사용합니다.
|
||||
|
||||
**소스**: `src/handlers/initMenuFromUrlHandler.ts`
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{
|
||||
"handler": "initMenuFromUrl"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### params
|
||||
|
||||
없음. 현재 URL 경로를 메뉴 항목과 매칭하여 활성 메뉴를 자동 설정합니다.
|
||||
|
||||
#### 동작
|
||||
|
||||
```text
|
||||
1. 현재 URL 경로 (window.location.pathname) 추출
|
||||
2. 사이드바 메뉴 데이터에서 URL 매칭
|
||||
3. 매칭된 메뉴 항목의 is_active 상태 설정
|
||||
4. 부모 메뉴도 자동으로 펼침 상태 설정
|
||||
```
|
||||
|
||||
#### 사용 예시 (_admin_base.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{ "handler": "initTheme" },
|
||||
{ "handler": "initMenuFromUrl" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 필터 가시성 핸들러
|
||||
|
||||
목록 화면에서 필터 패널의 가시성(표시/숨김)을 관리합니다. localStorage에 상태를 저장하여 새로고침 후에도 유지합니다.
|
||||
|
||||
**소스**: `src/handlers/filterVisibilityHandler.ts`
|
||||
|
||||
#### initFilterVisibility
|
||||
|
||||
저장된 필터 가시성 상태를 `_local`에 복원합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{
|
||||
"handler": "initFilterVisibility"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### saveFilterVisibility
|
||||
|
||||
현재 필터 가시성 상태를 localStorage에 저장합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "saveFilterVisibility",
|
||||
"params": {
|
||||
"filters": "{{_local.filterVisibility}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### toggleFilterVisibility
|
||||
|
||||
특정 필터 키의 가시성을 토글합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "toggleFilterVisibility",
|
||||
"params": {
|
||||
"key": "advancedFilters"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### resetFilterVisibility
|
||||
|
||||
모든 필터 가시성을 초기 상태로 리셋합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "resetFilterVisibility"
|
||||
}
|
||||
```
|
||||
|
||||
#### 핸들러 params 요약
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|--------|--------|------|
|
||||
| `initFilterVisibility` | 없음 | localStorage → `_local` 복원 |
|
||||
| `saveFilterVisibility` | `{ filters }` | `_local` → localStorage 저장 |
|
||||
| `toggleFilterVisibility` | `{ key }` | 특정 키 토글 |
|
||||
| `resetFilterVisibility` | 없음 | 전체 초기화 |
|
||||
|
||||
#### 사용 예시 (목록 페이지)
|
||||
|
||||
```json
|
||||
{
|
||||
"init_actions": [
|
||||
{ "handler": "initFilterVisibility" }
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"id": "filter_toggle_btn",
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": {
|
||||
"text": "$t:common.toggle_filters"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "toggleFilterVisibility",
|
||||
"params": { "key": "advancedFilters" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filter_section",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"if": "{{_local.filterVisibility?.advancedFilters}}",
|
||||
"children": [
|
||||
{ "comment": "필터 컴포넌트들" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 다국어 태그 핸들러
|
||||
|
||||
다국어 입력 컴포넌트(MultilingualInput)에서 사용하는 태그 관리 핸들러입니다.
|
||||
|
||||
**소스**: `src/handlers/multilingualTagHandler.ts`
|
||||
|
||||
#### saveMultilingualTag
|
||||
|
||||
다국어 태그를 저장합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "saveMultilingualTag",
|
||||
"params": {
|
||||
"field": "tags",
|
||||
"locale": "{{_global.locale}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### cancelMultilingualTag
|
||||
|
||||
다국어 태그 편집을 취소합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "cancelMultilingualTag"
|
||||
}
|
||||
```
|
||||
|
||||
#### updateMultilingualTagValue
|
||||
|
||||
다국어 태그 값을 업데이트합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"handler": "updateMultilingualTagValue",
|
||||
"params": {
|
||||
"field": "tags",
|
||||
"locale": "ko",
|
||||
"value": "{{$event.target.value}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 태그 핸들러 params 요약
|
||||
|
||||
| 핸들러 | params | 설명 |
|
||||
|--------|--------|------|
|
||||
| `saveMultilingualTag` | `{ field, locale }` | 태그 저장 |
|
||||
| `cancelMultilingualTag` | 없음 | 편집 취소 |
|
||||
| `updateMultilingualTagValue` | `{ field, locale, value }` | 값 업데이트 |
|
||||
|
||||
---
|
||||
|
||||
### 핸들러 소스 파일 매핑
|
||||
|
||||
| 핸들러명 | 소스 파일 | 등록 함수 |
|
||||
|---------|----------|----------|
|
||||
| `setLocale` | `src/handlers/setLocaleHandler.ts` | `setLocaleHandler` |
|
||||
| `setTheme`, `initTheme` | `src/handlers/setThemeHandler.ts` | `initTheme` |
|
||||
| `scrollToSection` | `src/handlers/scrollToSectionHandler.ts` | `scrollToSectionHandler` |
|
||||
| `initMenuFromUrl` | `src/handlers/initMenuFromUrlHandler.ts` | `initMenuFromUrlHandler` |
|
||||
| `initFilterVisibility`, `saveFilterVisibility`, `toggleFilterVisibility`, `resetFilterVisibility` | `src/handlers/filterVisibilityHandler.ts` | `initFilterVisibilityHandler` |
|
||||
| `saveMultilingualTag`, `cancelMultilingualTag`, `updateMultilingualTagValue` | `src/handlers/multilingualTagHandler.ts` | `saveMultilingualTagHandler` |
|
||||
|
||||
---
|
||||
|
||||
### 주의사항
|
||||
|
||||
```text
|
||||
이 핸들러들은 sirsoft-admin_basic 템플릿에서만 등록됨 (다른 템플릿에서 미지원 가능)
|
||||
범용 핸들러(navigate, apiCall, setState 등)와 달리 템플릿 의존적
|
||||
✅ 커스텀 핸들러이므로 template.json의 핸들러 등록 확인 필요
|
||||
✅ 범용 핸들러는 actions-handlers.md 참조
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 관련 문서
|
||||
|
||||
- [액션 핸들러 개요](../../../../docs/frontend/actions-handlers.md)
|
||||
- [sirsoft-admin_basic 컴포넌트](components.md)
|
||||
- [sirsoft-admin_basic 레이아웃](layouts.md)
|
||||
- [sirsoft-basic 핸들러](../../../../docs/frontend/templates/sirsoft-basic/handlers.md)
|
||||
@@ -0,0 +1,621 @@
|
||||
# Admin Basic — 레이아웃
|
||||
|
||||
> 레이아웃 목록과 라우트 매핑 · 진입점: [AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 레이아웃 목록
|
||||
|
||||
<!-- @generated:layouts START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
레이아웃 145개 (루트: `layouts`).
|
||||
|
||||
| 그룹 | 개수 |
|
||||
|---|---|
|
||||
| `(root)` | 27개 |
|
||||
| `auth` | 1개 |
|
||||
| `errors` | 6개 |
|
||||
| `overrides` | 1개 |
|
||||
| `partials` | 110개 |
|
||||
|
||||
| 레이아웃 | 그룹 | 종류 | extends |
|
||||
|---|---|---|---|
|
||||
| `_admin_base` | `(root)` | partial | - |
|
||||
| `admin_activity_log_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_dashboard` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_forgot_password` | `(root)` | 화면 | - |
|
||||
| `admin_identity_logs` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_language_pack_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_language_packs` | `(root)` | 화면 | `admin_language_pack_list` |
|
||||
| `admin_language_packs_install_modal` | `(root)` | 화면 | - |
|
||||
| `admin_login` | `(root)` | 화면 | - |
|
||||
| `admin_menu_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_module_language_packs` | `(root)` | 화면 | `admin_language_pack_list` |
|
||||
| `admin_module_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_notification_log_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_plugin_language_packs` | `(root)` | 화면 | `admin_language_pack_list` |
|
||||
| `admin_plugin_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_reset_password` | `(root)` | 화면 | - |
|
||||
| `admin_role_form` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_role_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_schedule_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_settings` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_template_language_packs` | `(root)` | 화면 | `admin_language_pack_list` |
|
||||
| `admin_template_layout_edit` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_template_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_user_detail` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_user_form` | `(root)` | 화면 | `_admin_base` |
|
||||
| `admin_user_list` | `(root)` | 화면 | `_admin_base` |
|
||||
| `identity_challenge` | `auth` | 화면 | `_admin_base` |
|
||||
| `401` | `errors` | 화면 | `_admin_base` |
|
||||
| `403` | `errors` | 화면 | `_admin_base` |
|
||||
| `404` | `errors` | 화면 | `_admin_base` |
|
||||
| `500` | `errors` | 화면 | `_admin_base` |
|
||||
| `503` | `errors` | 화면 | `_admin_base` |
|
||||
| `maintenance` | `errors` | 화면 | `_admin_base` |
|
||||
| `index` | `overrides` | 화면 | `_admin_base` |
|
||||
| `_identity_challenge_modal` | `partials` | partial | - |
|
||||
| `_modal_changelog` | `partials` | partial | - |
|
||||
| `_modal_license` | `partials` | partial | - |
|
||||
| `_modal_notification_delete_all_confirm` | `partials` | partial | - |
|
||||
| `_partial_datagrid` | `partials` | partial | - |
|
||||
| `_partial_filter` | `partials` | partial | - |
|
||||
| `_content` | `partials` | partial | - |
|
||||
| `_modal_log_detail` | `partials` | partial | - |
|
||||
| `_modal_purge_confirm` | `partials` | partial | - |
|
||||
| `_partial_datagrid` | `partials` | partial | - |
|
||||
| `_partial_filter` | `partials` | partial | - |
|
||||
| `_content` | `partials` | partial | - |
|
||||
| `_drawer_manifest_preview` | `partials` | partial | - |
|
||||
| `_modal_detail` | `partials` | partial | - |
|
||||
| `_modal_install` | `partials` | partial | - |
|
||||
| `_modal_install_bundled` | `partials` | partial | - |
|
||||
| `_modal_refresh_cache` | `partials` | partial | - |
|
||||
| `_modal_slot_conflict` | `partials` | partial | - |
|
||||
| `_modal_uninstall` | `partials` | partial | - |
|
||||
| `_modal_update` | `partials` | partial | - |
|
||||
| `_modal_delete` | `partials` | partial | - |
|
||||
| `_panel_detail` | `partials` | partial | - |
|
||||
| `_panel_form` | `partials` | partial | - |
|
||||
| `_panel_menu_list` | `partials` | partial | - |
|
||||
| `_panel_view` | `partials` | partial | - |
|
||||
| `_drawer_manifest_preview` | `partials` | partial | - |
|
||||
| `_modal_deactivate_warning` | `partials` | partial | - |
|
||||
| `_modal_detail` | `partials` | partial | - |
|
||||
| `_modal_extension_license` | `partials` | partial | - |
|
||||
| `_modal_force_activate` | `partials` | partial | - |
|
||||
| `_modal_force_deactivate` | `partials` | partial | - |
|
||||
| `_modal_install` | `partials` | partial | - |
|
||||
| `_modal_manual_install` | `partials` | partial | - |
|
||||
| `_modal_reactivate_language_packs` | `partials` | partial | - |
|
||||
| `_modal_refresh_layouts` | `partials` | partial | - |
|
||||
| `_modal_uninstall` | `partials` | partial | - |
|
||||
| `_modal_update` | `partials` | partial | - |
|
||||
| `_modal_log_detail` | `partials` | partial | - |
|
||||
| `_partial_datagrid` | `partials` | partial | - |
|
||||
| `_partial_filter` | `partials` | partial | - |
|
||||
| `_drawer_manifest_preview` | `partials` | partial | - |
|
||||
| `_modal_detail` | `partials` | partial | - |
|
||||
| `_modal_extension_license` | `partials` | partial | - |
|
||||
| `_modal_force_activate` | `partials` | partial | - |
|
||||
| `_modal_force_deactivate` | `partials` | partial | - |
|
||||
| `_modal_install` | `partials` | partial | - |
|
||||
| `_modal_manual_install` | `partials` | partial | - |
|
||||
| `_modal_reactivate_language_packs` | `partials` | partial | - |
|
||||
| `_modal_refresh_layouts` | `partials` | partial | - |
|
||||
| `_modal_uninstall` | `partials` | partial | - |
|
||||
| `_modal_update` | `partials` | partial | - |
|
||||
| `_modal_delete` | `partials` | partial | - |
|
||||
| `_modal_delete` | `partials` | partial | - |
|
||||
| `_modal_duplicate` | `partials` | partial | - |
|
||||
| `_modal_form` | `partials` | partial | - |
|
||||
| `_modal_history` | `partials` | partial | - |
|
||||
| `_modal_run` | `partials` | partial | - |
|
||||
| `_tab_schedules` | `partials` | partial | - |
|
||||
| `_modal_cache_delete` | `partials` | partial | - |
|
||||
| `_modal_core_changelog` | `partials` | partial | - |
|
||||
| `_modal_core_update_guide` | `partials` | partial | - |
|
||||
| `_modal_core_update_result` | `partials` | partial | - |
|
||||
| `_modal_identity_message_definition_add` | `partials` | partial | - |
|
||||
| `_modal_identity_message_definition_delete` | `partials` | partial | - |
|
||||
| `_modal_identity_message_definition_reset` | `partials` | partial | - |
|
||||
| `_modal_identity_message_template_form` | `partials` | partial | - |
|
||||
| `_modal_identity_message_template_preview` | `partials` | partial | - |
|
||||
| `_modal_identity_policy_delete` | `partials` | partial | - |
|
||||
| `_modal_identity_policy_form` | `partials` | partial | - |
|
||||
| `_modal_mail_template_form` | `partials` | partial | - |
|
||||
| `_modal_notification_definition_reset` | `partials` | partial | - |
|
||||
| `_modal_notification_template_form` | `partials` | partial | - |
|
||||
| `_modal_notification_template_preview` | `partials` | partial | - |
|
||||
| `_modal_password_confirm` | `partials` | partial | - |
|
||||
| `_tab_advanced` | `partials` | partial | - |
|
||||
| `_tab_drivers` | `partials` | partial | - |
|
||||
| `_tab_general` | `partials` | partial | - |
|
||||
| `_tab_identity` | `partials` | partial | - |
|
||||
| `_tab_identity_basic` | `partials` | partial | - |
|
||||
| `_tab_identity_messages` | `partials` | partial | - |
|
||||
| `_tab_identity_policies` | `partials` | partial | - |
|
||||
| `_tab_identity_providers` | `partials` | partial | - |
|
||||
| `_tab_info` | `partials` | partial | - |
|
||||
| `_tab_language_packs` | `partials` | partial | - |
|
||||
| `_tab_mail` | `partials` | partial | - |
|
||||
| `_tab_mail_templates` | `partials` | partial | - |
|
||||
| `_tab_notification_definitions` | `partials` | partial | - |
|
||||
| `_tab_security` | `partials` | partial | - |
|
||||
| `_tab_seo` | `partials` | partial | - |
|
||||
| `_tab_upload` | `partials` | partial | - |
|
||||
| `_modal_extension_preview_layout` | `partials` | partial | - |
|
||||
| `_modal_extension_version_history` | `partials` | partial | - |
|
||||
| `_modal_version_history` | `partials` | partial | - |
|
||||
| `_drawer_manifest_preview` | `partials` | partial | - |
|
||||
| `_modal_activate` | `partials` | partial | - |
|
||||
| `_modal_deactivate` | `partials` | partial | - |
|
||||
| `_modal_detail` | `partials` | partial | - |
|
||||
| `_modal_extension_license` | `partials` | partial | - |
|
||||
| `_modal_force_activate` | `partials` | partial | - |
|
||||
| `_modal_install` | `partials` | partial | - |
|
||||
| `_modal_manual_install` | `partials` | partial | - |
|
||||
| `_modal_reactivate_language_packs` | `partials` | partial | - |
|
||||
| `_modal_refresh_layouts` | `partials` | partial | - |
|
||||
| `_modal_uninstall` | `partials` | partial | - |
|
||||
| `_modal_update` | `partials` | partial | - |
|
||||
| `_tab_admin` | `partials` | partial | - |
|
||||
| `_tab_user` | `partials` | partial | - |
|
||||
| `_content_section` | `partials` | partial | - |
|
||||
| `_header_section` | `partials` | partial | - |
|
||||
| `_info_card` | `partials` | partial | - |
|
||||
| `template_partial_test` | `(root)` | 화면 | `_admin_base` |
|
||||
<!-- @generated:layouts END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
그룹은 "이 파일이 독립된 화면 URL을 갖는가"로 나뉩니다 — `(root)`/`auth`/`errors`/`overrides` 는
|
||||
라우트에 직접 매핑되는 화면이고, `partials`(110개, 전체의 76%)는 화면에 `extends`/포함되는
|
||||
조각(탭·모달·패널)이라 그 자체로는 URL이 없습니다. partial 비중이 압도적으로 큰 것은 관리자
|
||||
화면 하나가 보통 탭 여러 개 + 모달 여러 개로 구성되기 때문입니다(예: 확장 관리 화면 하나가
|
||||
설치/삭제/업데이트/강제활성화 등 모달을 5~10개씩 갖습니다).
|
||||
|
||||
이 목록은 코드에서 실측되므로 구체적인 개수·파일명을 프로즈에 하드코딩해 반복하지 않습니다
|
||||
— 과거 버전 문서가 손으로 그린 전체 페이지 트리(약 20개 화면 기준)는 이미 145개로 늘어난
|
||||
현재 상태와 크게 어긋나 있었습니다. 전체 목록은 항상 위 생성 표를 신뢰합니다.
|
||||
|
||||
### 화면 유형별 구성 패턴
|
||||
|
||||
새 관리자 화면을 만들 때 아래 패턴 중 가장 가까운 것을 참고합니다(구체적 파일명이 아니라
|
||||
**구조**를 재사용하는 것이 목적입니다 — 실제 예시 파일은 위 표에서 비슷한 이름을 찾습니다).
|
||||
|
||||
**목록 화면**: `extends: _admin_base`, `slots.content` 에 `PageHeader`(제목·액션) →
|
||||
`FilterGroup`(선택) → `DataGrid`/`CardGrid`(목록) → `Pagination`. data_sources 는
|
||||
`auto_fetch: true` + `params`에 `_local.page`/`per_page` 바인딩. 삭제·상태변경은 `apiCall`,
|
||||
상세/수정 이동은 `navigate`, 필터·페이지네이션은 `setState`. 목록 컨텍스트 왕복 규약
|
||||
(`mergeQuery`)을 지킵니다(§CLAUDE.md).
|
||||
|
||||
**상세 화면**: `extends: _admin_base`, `data_sources`에 `route.id` 기반 상세 API,
|
||||
`slots.content`에 `PageHeader` + `Card` 여러 개(기본 정보/활동 내역/권한 정보 등 섹션별 분리).
|
||||
|
||||
**폼 화면(생성/수정 겸용)**: `route.id` 존재 여부로 생성/수정을 분기, `Form` 안에
|
||||
`FormField`+`Input`/`Select`/`Toggle` 조합. `Button` 은 `type="button"` 명시(submit 방지),
|
||||
서버 검증 에러는 `FormField` 의 `error` prop 으로 표시.
|
||||
|
||||
**설정(탭) 화면**: `TabNavigation` + `activeTab` 상태에 따라 `_tab_*.json` partial 을
|
||||
조건부 렌더링. 탭 전환은 `setState`, 저장은 `apiCall`, 파괴적 동작 확인은 `openModal`.
|
||||
|
||||
**확장 관리 화면**(모듈/플러그인/템플릿 목록): `DataGrid`/`CardGrid` + `StatusBadge`(상태) +
|
||||
`ActionMenu`(설치/활성화/비활성화/삭제/업데이트) + `ExtensionBadge`. 설치·삭제·업데이트마다
|
||||
전용 확인 모달(`_modal_install`/`_modal_uninstall`/`_modal_update` 등)을 개별 파일로 분리 —
|
||||
한 모달에 여러 동작을 조건 분기로 몰아넣지 않습니다(모달마다 문구·부작용이 다릅니다).
|
||||
|
||||
**에러 화면**(`errors/*.json`): `extends` 없는 독립 레이아웃. `Div`(중앙 정렬) 안에
|
||||
`Icon`+`H1`(코드)+`P`(메시지)+`Button`(홈 이동). 독립 레이아웃이므로 `Toast`/`Modal` 같은
|
||||
전역 호스트 컴포넌트가 필요하면 직접 마운트해야 합니다(§CLAUDE.md "독립 레이아웃의 글로벌
|
||||
호스트 컴포넌트").
|
||||
|
||||
### `_admin_base.json` 에 대한 정정
|
||||
|
||||
과거 버전 문서는 `_admin_base.json` 이 `init_actions: [initTheme, initMenuFromUrl]` 를 갖는다고
|
||||
적었으나, 현재 `_admin_base.json` 에는 `init_actions` 키 자체가 없습니다 — 두 핸들러는 현재
|
||||
`_admin_base` 를 상속하지 않는 인증 화면(`admin_login`/`admin_forgot_password`/
|
||||
`admin_reset_password`)에서만 호출됩니다. 사이드바 접힘 상태 복원(`initSidebar`)은 레이아웃이
|
||||
아니라 템플릿 부트스트랩(`src/index.ts`)에서 직접 호출됩니다. `_admin_base` 를 고칠 때 이
|
||||
문서의 낡은 구조를 그대로 믿지 말고 실제 JSON 을 확인하세요.
|
||||
<!-- @intent END -->
|
||||
|
||||
## 라우트 매핑
|
||||
|
||||
<!-- @generated:layout-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
| 경로 | 레이아웃 | 이름 |
|
||||
|---|---|---|
|
||||
| `*/admin` | `-` | - |
|
||||
| `*/admin/login` | `admin_login` | - |
|
||||
| `*/admin/forgot-password` | `admin_forgot_password` | - |
|
||||
| `*/admin/reset-password` | `admin_reset_password` | - |
|
||||
| `*/admin/dashboard` | `admin_dashboard` | - |
|
||||
| `*/admin/users` | `admin_user_list` | - |
|
||||
| `*/admin/users/create` | `admin_user_form` | - |
|
||||
| `*/admin/users/:id` | `admin_user_detail` | - |
|
||||
| `*/admin/users/:id/edit` | `admin_user_form` | - |
|
||||
| `*/admin/modules` | `admin_module_list` | - |
|
||||
| `*/admin/settings/language-packs` | `-` | - |
|
||||
| `*/admin/modules/:identifier/language-packs` | `admin_module_language_packs` | - |
|
||||
| `*/admin/plugins/:identifier/language-packs` | `admin_plugin_language_packs` | - |
|
||||
| `*/admin/templates/:identifier/language-packs` | `admin_template_language_packs` | - |
|
||||
| `*/admin/plugins` | `admin_plugin_list` | - |
|
||||
| `*/admin/menus` | `admin_menu_list` | - |
|
||||
| `*/admin/templates` | `-` | - |
|
||||
| `*/admin/templates/:identifier/edit` | `admin_template_layout_edit` | - |
|
||||
| `*/admin/templates/:type` | `admin_template_list` | - |
|
||||
| `*/admin/template/partial` | `template_partial_test` | - |
|
||||
| `*/admin/activity-logs` | `admin_activity_log_list` | - |
|
||||
| `*/admin/notification-logs` | `admin_notification_log_list` | - |
|
||||
| `*/admin/settings` | `admin_settings` | - |
|
||||
| `*/admin/roles` | `admin_role_list` | - |
|
||||
| `*/admin/roles/create` | `admin_role_form` | - |
|
||||
| `*/admin/roles/:id/edit` | `admin_role_form` | - |
|
||||
| `*/admin/schedules` | `admin_schedule_list` | - |
|
||||
| `*/admin/identity/logs` | `admin_identity_logs` | - |
|
||||
| `*/admin/identity/challenge` | `auth/identity_challenge` | - |
|
||||
<!-- @generated:layout-map END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
`레이아웃` 열이 `-` 인 두 행(`*/admin`, `*/admin/settings/language-packs`)은 레이아웃이
|
||||
없다는 뜻이 아니라 **다른 라우트로 리다이렉트되는 진입점**입니다 — 예를 들어 `/admin` 은
|
||||
로그인 여부에 따라 `/admin/login` 또는 `/admin/dashboard` 로 넘어가는 게이트 라우트입니다.
|
||||
새 화면을 추가할 때 이 표에 라우트를 등록하는 것만으로 끝나지 않습니다 — 사이드바 메뉴에서
|
||||
그 화면으로 이동하는 진입점도 함께 추가해야 실제로 도달 가능해집니다(라우트만 있고 메뉴
|
||||
항목이 없으면 URL을 직접 입력해야만 닿는 화면이 됩니다).
|
||||
<!-- @intent END -->
|
||||
|
||||
## 확장 오버라이드
|
||||
|
||||
<!-- @generated:template-overrides START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
||||
_오버라이드하는 레이아웃 확장 조각이 없습니다._
|
||||
<!-- @generated:template-overrides END -->
|
||||
|
||||
<!-- @intent START -->
|
||||
이 템플릿은 현재 어떤 모듈/플러그인의 레이아웃 확장 조각도 오버라이드하지 않습니다 —
|
||||
`sirsoft-basic` 템플릿과 달리 관리자 화면은 확장이 끼워 넣는 조각(예: 이커머스 문의 설정,
|
||||
GDPR 배너)을 코어 대시보드 위젯 형태로만 받고, 이 템플릿이 그 조각을 대체할 필요가 아직
|
||||
없었기 때문입니다. 특정 확장의 관리자 UI 를 이 템플릿에서만 다르게 보이게 하려면
|
||||
`extensions/{module-identifier}/*.json` 을 신설합니다(§docs/extension/layout-extensions.md
|
||||
"템플릿 오버라이드").
|
||||
<!-- @intent END -->
|
||||
|
||||
## 이관 원문 상세
|
||||
|
||||
> 아래는 코어 `docs/frontend/templates/sirsoft-admin_basic/layouts.md` 에 있던 원문을
|
||||
> 이 문서로 옮긴 것입니다(#601). 페이지 맵 트리와 화면 유형별 패턴 상세가 여기에 있습니다.
|
||||
> 이관 시점 그대로 보존하되, 현재 코드와 어긋나는 부분에는 정정 주석을 달았습니다 —
|
||||
> 레이아웃·라우트의 SSoT 는 위 「레이아웃 목록」·「라우트 매핑」 블록입니다.
|
||||
|
||||
### 페이지 맵 (트리 구조)
|
||||
|
||||
```text
|
||||
_admin_base.json (베이스 레이아웃)
|
||||
│
|
||||
├── admin_dashboard.json (대시보드)
|
||||
├── admin_login.json (로그인 — _admin_base 미상속)
|
||||
│
|
||||
├── 사용자 관리
|
||||
│ ├── admin_user_list.json (목록)
|
||||
│ ├── admin_user_form.json (생성/수정)
|
||||
│ └── admin_user_detail.json (상세)
|
||||
│
|
||||
├── 역할 관리
|
||||
│ ├── admin_role_list.json (목록)
|
||||
│ │ └── partials/admin_role_list/_modal_delete.json
|
||||
│ └── admin_role_form.json (생성/수정)
|
||||
│
|
||||
├── 메뉴 관리
|
||||
│ └── admin_menu_list.json (3패널 레이아웃)
|
||||
│ └── partials/admin_menu_list/
|
||||
│ ├── _panel_menu_list.json (좌측: 메뉴 트리)
|
||||
│ ├── _panel_form.json (중앙: 편집 폼)
|
||||
│ ├── _panel_detail.json (중앙: 상세 보기)
|
||||
│ ├── _panel_view.json (우측: 미리보기)
|
||||
│ └── _modal_delete.json
|
||||
│
|
||||
├── 환경 설정
|
||||
│ └── admin_settings.json (탭 네비게이션)
|
||||
│ └── partials/admin_settings/
|
||||
│ ├── _tab_general.json (일반)
|
||||
│ ├── _tab_mail.json (메일 발송 SMTP 설정)
|
||||
│ ├── _tab_notification_definitions.json (알림 정의)
|
||||
│ ├── _tab_security.json (보안)
|
||||
│ ├── _tab_upload.json (업로드)
|
||||
│ ├── _tab_drivers.json (드라이버)
|
||||
│ ├── _tab_seo.json (SEO)
|
||||
│ ├── _tab_advanced.json (고급)
|
||||
│ ├── _tab_info.json (시스템 정보)
|
||||
│ ├── _modal_cache_delete.json
|
||||
│ ├── _modal_core_changelog.json
|
||||
│ ├── _modal_core_update_guide.json
|
||||
│ ├── _modal_core_update_result.json
|
||||
│ ├── _modal_notification_template_form.json
|
||||
│ ├── _modal_notification_template_preview.json
|
||||
│ └── _modal_password_confirm.json
|
||||
│
|
||||
├── 모듈 관리
|
||||
│ └── admin_module_list.json
|
||||
│ └── partials/admin_module_list/
|
||||
│ ├── _modal_detail.json
|
||||
│ ├── _modal_install.json
|
||||
│ ├── _modal_manual_install.json
|
||||
│ ├── _modal_uninstall.json
|
||||
│ ├── _modal_update.json
|
||||
│ ├── _modal_deactivate_warning.json
|
||||
│ ├── _modal_force_activate.json
|
||||
│ ├── _modal_force_deactivate.json
|
||||
│ ├── _modal_extension_license.json
|
||||
│ └── _modal_refresh_layouts.json
|
||||
│
|
||||
├── 플러그인 관리
|
||||
│ └── admin_plugin_list.json
|
||||
│ └── partials/admin_plugin_list/
|
||||
│ ├── (모듈과 동일 구조 — 10개 모달)
|
||||
│ └── ...
|
||||
│
|
||||
├── 템플릿 관리
|
||||
│ ├── admin_template_list.json
|
||||
│ │ └── partials/admin_template_list/
|
||||
│ │ ├── _tab_admin.json (Admin 템플릿 탭)
|
||||
│ │ ├── _tab_user.json (User 템플릿 탭)
|
||||
│ │ ├── _modal_detail.json
|
||||
│ │ ├── _modal_install.json
|
||||
│ │ ├── _modal_manual_install.json
|
||||
│ │ ├── _modal_uninstall.json
|
||||
│ │ ├── _modal_update.json
|
||||
│ │ ├── _modal_activate.json
|
||||
│ │ ├── _modal_deactivate.json
|
||||
│ │ ├── _modal_force_activate.json
|
||||
│ │ ├── _modal_extension_license.json
|
||||
│ │ └── _modal_refresh_layouts.json
|
||||
│ └── admin_template_layout_edit.json (레이아웃 편집기)
|
||||
│ └── partials/admin_template_layout_edit/_modal_version_history.json
|
||||
│
|
||||
├── 스케줄 관리
|
||||
│ └── admin_schedule_list.json
|
||||
│ └── partials/admin_schedule_list/
|
||||
│ ├── _tab_schedules.json
|
||||
│ ├── _modal_form.json
|
||||
│ ├── _modal_delete.json
|
||||
│ ├── _modal_duplicate.json
|
||||
│ ├── _modal_history.json
|
||||
│ └── _modal_run.json
|
||||
│
|
||||
├── 메일 발송 로그
|
||||
│ └── admin_mail_send_log_list.json
|
||||
│ └── partials/admin_mail_send_log_list/
|
||||
│ ├── _partial_datagrid.json
|
||||
│ └── _partial_filter.json
|
||||
│
|
||||
├── 공통 Partial
|
||||
│ ├── partials/_modal_changelog.json
|
||||
│ └── partials/_modal_license.json
|
||||
│
|
||||
├── 에러 페이지
|
||||
│ └── errors/
|
||||
│ ├── 401.json (인증 필요)
|
||||
│ ├── 403.json (접근 거부)
|
||||
│ ├── 404.json (페이지 없음)
|
||||
│ ├── 500.json (서버 오류)
|
||||
│ ├── 503.json (서비스 불가)
|
||||
│ └── maintenance.json (점검 중)
|
||||
│
|
||||
├── 오버라이드
|
||||
│ └── overrides/sirsoft-sample/index.json
|
||||
│
|
||||
└── 테스트
|
||||
└── template_partial_test.json
|
||||
└── partials/template_partial_test/
|
||||
├── _content_section.json
|
||||
├── _header_section.json
|
||||
└── _info_card.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 카테고리별 가이드
|
||||
|
||||
#### 목록 페이지 패턴
|
||||
|
||||
**대표**: `admin_user_list.json`, `admin_role_list.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _admin_base
|
||||
slots.content:
|
||||
└── PageHeader (제목, 액션 버튼)
|
||||
└── FilterGroup (선택적)
|
||||
└── DataGrid (columns, data, pagination)
|
||||
└── Pagination
|
||||
```
|
||||
|
||||
**data_sources**:
|
||||
```json
|
||||
{
|
||||
"id": "users",
|
||||
"endpoint": "/api/admin/users",
|
||||
"method": "GET",
|
||||
"auto_fetch": true,
|
||||
"params": { "page": "{{_local.page ?? 1}}", "per_page": "{{_local.per_page ?? 15}}" }
|
||||
}
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 삭제, 상태 변경
|
||||
- `navigate` — 상세/수정 페이지 이동
|
||||
- `setState` — 필터, 페이지네이션 상태
|
||||
|
||||
**Partial 구조**: 모달 (삭제 확인, 상세 보기 등)
|
||||
|
||||
---
|
||||
|
||||
#### 상세 페이지 패턴
|
||||
|
||||
**대표**: `admin_user_detail.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _admin_base
|
||||
data_sources: [상세 API (route.id 기반)]
|
||||
slots.content:
|
||||
└── PageHeader
|
||||
└── Card (기본 정보 섹션)
|
||||
└── Card (활동 내역 섹션)
|
||||
└── Card (권한 정보 섹션)
|
||||
```
|
||||
|
||||
**data_sources**:
|
||||
```json
|
||||
{
|
||||
"id": "user",
|
||||
"endpoint": "/api/admin/users/{{route.id}}",
|
||||
"method": "GET",
|
||||
"auto_fetch": true
|
||||
}
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 상태 변경 (활성화/비활성화, 역할 변경)
|
||||
- `navigate` — 목록으로 이동, 수정 페이지 이동
|
||||
|
||||
---
|
||||
|
||||
#### 폼 페이지 패턴
|
||||
|
||||
**대표**: `admin_user_form.json`, `admin_role_form.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _admin_base
|
||||
data_sources: [상세 API (수정 시), 참조 데이터 (Select 옵션)]
|
||||
slots.content:
|
||||
└── PageHeader
|
||||
└── Form
|
||||
├── FormField + Input (텍스트)
|
||||
├── FormField + Select (선택)
|
||||
├── FormField + Toggle (토글)
|
||||
└── Button (저장/취소)
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 생성 (POST) / 수정 (PUT)
|
||||
- `navigate` — 성공 후 목록/상세로 이동
|
||||
|
||||
**주의사항**:
|
||||
```text
|
||||
✅ Form 내 Button에 type="button" 명시 (submit 방지)
|
||||
✅ 수정 폼은 route.id 존재 여부로 생성/수정 구분
|
||||
✅ FormField에 error prop으로 서버 검증 에러 표시
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 설정 페이지 패턴
|
||||
|
||||
**대표**: `admin_settings.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _admin_base
|
||||
data_sources: [설정 API]
|
||||
slots.content:
|
||||
└── PageHeader
|
||||
└── TabNavigation (tabs)
|
||||
└── Div (탭별 partial 조건부 렌더링)
|
||||
├── if: activeTab === 'general' → partial: _tab_general.json
|
||||
├── if: activeTab === 'mail' → partial: _tab_mail.json
|
||||
├── if: activeTab === 'security' → partial: _tab_security.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Partial 구조**: `partials/admin_settings/_tab_*.json` (9개 탭)
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `setState` — 탭 전환
|
||||
- `apiCall` — 설정 저장
|
||||
- `openModal` — 확인 다이얼로그
|
||||
|
||||
---
|
||||
|
||||
#### 확장 관리 패턴
|
||||
|
||||
**대표**: `admin_module_list.json`, `admin_plugin_list.json`, `admin_template_list.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
extends: _admin_base
|
||||
data_sources: [확장 목록 API]
|
||||
slots.content:
|
||||
└── PageHeader (새로고침, 수동 설치 버튼)
|
||||
└── DataGrid/CardGrid (확장 목록)
|
||||
├── StatusBadge (상태)
|
||||
├── ActionMenu (설치/활성화/비활성화/삭제/업데이트)
|
||||
└── ExtensionBadge (모듈 식별)
|
||||
modals:
|
||||
├── _modal_detail.json (상세 정보)
|
||||
├── _modal_install.json (설치 확인)
|
||||
├── _modal_uninstall.json (삭제 확인)
|
||||
├── _modal_update.json (업데이트)
|
||||
└── _modal_force_activate.json 등
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `apiCall` — 설치, 활성화, 비활성화, 삭제, 업데이트
|
||||
- `openModal` — 확인 다이얼로그
|
||||
- `setState` — 선택된 확장 정보 저장
|
||||
|
||||
**특수사항**:
|
||||
- 템플릿 관리는 Admin/User 탭 분리 (`_tab_admin.json`, `_tab_user.json`)
|
||||
- 레이아웃 편집기 (`admin_template_layout_edit.json`)는 CodeEditor + 실시간 미리보기
|
||||
|
||||
---
|
||||
|
||||
#### 에러 페이지 패턴
|
||||
|
||||
**대표**: `errors/404.json`
|
||||
|
||||
**구성**:
|
||||
```text
|
||||
(extends 없음 — 독립 레이아웃)
|
||||
components:
|
||||
└── Div (전체 화면 중앙 정렬)
|
||||
├── Icon (에러 아이콘)
|
||||
├── H1 (에러 코드)
|
||||
├── P (에러 메시지)
|
||||
└── Button (홈으로 이동)
|
||||
```
|
||||
|
||||
**핸들러 패턴**:
|
||||
- `navigate` — 대시보드/홈으로 이동
|
||||
|
||||
---
|
||||
|
||||
### 베이스 레이아웃 구조
|
||||
|
||||
#### _admin_base.json
|
||||
|
||||
모든 관리자 페이지의 공통 구조를 정의합니다.
|
||||
|
||||
```text
|
||||
_admin_base.json
|
||||
├── init_actions: [initTheme, initMenuFromUrl]
|
||||
├── data_sources: [admin_menu, notifications]
|
||||
├── components:
|
||||
│ ├── AdminSidebar (menu: admin_menu.data)
|
||||
│ ├── AdminHeader (user, notifications)
|
||||
│ ├── Toast
|
||||
│ ├── PageTransitionIndicator
|
||||
│ └── Div (content area)
|
||||
│ └── slot: "content" (← 하위 레이아웃이 채움)
|
||||
└── AdminFooter
|
||||
```
|
||||
|
||||
**슬롯**:
|
||||
- `content` — 각 페이지의 메인 콘텐츠가 삽입되는 위치
|
||||
|
||||
---
|
||||
|
||||
### 관련 문서
|
||||
|
||||
- [sirsoft-admin_basic 컴포넌트](components.md)
|
||||
- [sirsoft-admin_basic 핸들러](handlers.md)
|
||||
- [레이아웃 JSON 스키마](../../../../docs/frontend/layout-json.md)
|
||||
- [레이아웃 상속](../../../../docs/frontend/layout-json-inheritance.md)
|
||||
- [sirsoft-basic 레이아웃](../../../../docs/frontend/templates/sirsoft-basic/layouts.md)
|
||||
Reference in New Issue
Block a user