docs: update user menu development rules
- Added critical guidelines prohibiting the direct coding of user menu screens in React (.tsx) files, emphasizing the use of the low-code screen designer system. - Outlined the correct implementation steps for user menus, including the registration of screens in the `screen_definitions` and `screen_layouts_v2` tables, and the necessity of menu registration in `menu_info`. - Highlighted prohibited patterns to prevent hardcoding user interface components, ensuring adherence to the established rendering system. Made-with: Cursor
This commit is contained in:
parent
5fb1f705dc
commit
7cb0be14ab
|
|
@ -12,13 +12,14 @@
|
|||
- **대상**: 시스템 설정, 사용자 관리, 결재 관리, 코드 관리 등
|
||||
- **특징**: 하드코딩된 UI, 관리자만 접근
|
||||
|
||||
### 사용자 메뉴 (User/Screen)
|
||||
### 사용자 메뉴 (User/Screen) - 절대 하드코딩 금지!!!
|
||||
- **구현 방식**: 로우코드 기반 (DB에 JSON으로 화면 구성 저장)
|
||||
- **데이터 저장**: `screen_layouts` 테이블에 JSON 형식 보관
|
||||
- **데이터 저장**: `screen_layouts_v2` 테이블에 V2 JSON 형식 보관
|
||||
- **화면 디자이너**: 스크린 디자이너로 드래그앤드롭 구성
|
||||
- **V2 컴포넌트**: `frontend/lib/registry/components/v2-*` 디렉토리
|
||||
- **대상**: 일반 업무 화면, BOM, 문서 관리 등
|
||||
- **대상**: 일반 업무 화면, BOM, 문서 관리, 포장/적재, 금형 관리 등
|
||||
- **특징**: 코드 수정 없이 화면 구성 변경 가능
|
||||
- **절대 금지**: React `.tsx` 페이지 파일로 직접 UI를 하드코딩하는 것!
|
||||
|
||||
### 판단 기준
|
||||
|
||||
|
|
@ -26,8 +27,88 @@
|
|||
|------|-------------|-------------|
|
||||
| 누가 쓰나? | 시스템 관리자 | 일반 사용자 |
|
||||
| 화면 구조 고정? | 고정 (코드) | 유동적 (JSON) |
|
||||
| URL 패턴 | `/admin/*` | 스크린 디자이너 경유 |
|
||||
| 메뉴 등록 | `menu_info` INSERT 필수 | 스크린 레이아웃 등록 |
|
||||
| URL 패턴 | `/admin/*` | `/screen/{screen_code}` |
|
||||
| 메뉴 등록 | `menu_info` INSERT | `screen_definitions` + `menu_info` INSERT |
|
||||
| 프론트엔드 코드 | `frontend/app/(main)/admin/` 하위에 page.tsx 작성 | **코드 작성 금지!** DB에 스크린 정의만 등록 |
|
||||
|
||||
### 사용자 메뉴 구현 방법 (반드시 이 방식으로!)
|
||||
|
||||
**절대 규칙: 사용자 메뉴는 React 페이지(.tsx)를 직접 만들지 않는다!**
|
||||
이미 `/screen/[screenCode]/page.tsx` → `/screens/[screenId]/page.tsx` 렌더링 시스템이 존재한다.
|
||||
새 화면이 필요하면 DB에 등록만 하면 자동으로 렌더링된다.
|
||||
|
||||
#### Step 1: screen_definitions에 화면 등록
|
||||
|
||||
```sql
|
||||
INSERT INTO screen_definitions (screen_name, screen_code, table_name, company_code, is_active)
|
||||
VALUES ('포장/적재정보 관리', 'COMPANY_7_PKG', 'pkg_unit', 'COMPANY_7', 'Y')
|
||||
RETURNING screen_id;
|
||||
```
|
||||
|
||||
- `screen_code`: `{company_code}_{기능약어}` 형식 (예: COMPANY_7_PKG)
|
||||
- `table_name`: 메인 테이블명 (V2 컴포넌트가 이 테이블 기준으로 동작)
|
||||
- `company_code`: 대상 회사 코드
|
||||
|
||||
#### Step 2: screen_layouts_v2에 V2 레이아웃 JSON 등록
|
||||
|
||||
```sql
|
||||
INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layer_name, layout_data)
|
||||
VALUES (
|
||||
{screen_id},
|
||||
'COMPANY_7',
|
||||
1,
|
||||
'기본 레이어',
|
||||
'{
|
||||
"version": "2.0",
|
||||
"components": [
|
||||
{
|
||||
"id": "comp_split_1",
|
||||
"url": "@/lib/registry/components/v2-split-panel-layout",
|
||||
"position": {"x": 0, "y": 0},
|
||||
"size": {"width": 1200, "height": 800},
|
||||
"displayOrder": 0,
|
||||
"overrides": {
|
||||
"leftTitle": "포장단위 목록",
|
||||
"rightTitle": "상세 정보",
|
||||
"splitRatio": 40,
|
||||
"leftTableName": "pkg_unit",
|
||||
"rightTableName": "pkg_unit",
|
||||
"tabs": [
|
||||
{"id": "basic", "label": "기본정보"},
|
||||
{"id": "items", "label": "매칭품목"}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}'::jsonb
|
||||
);
|
||||
```
|
||||
|
||||
- V2 컴포넌트 목록: v2-split-panel-layout, v2-table-list, v2-table-search-widget, v2-repeater, v2-button-primary, v2-tabs-widget 등
|
||||
- 상세 컴포넌트 가이드: `.cursor/rules/component-development-guide.mdc` 참조
|
||||
|
||||
#### Step 3: menu_info에 메뉴 등록
|
||||
|
||||
```sql
|
||||
-- 먼저 부모 메뉴 objid 조회
|
||||
-- SELECT objid, menu_name_kor FROM menu_info WHERE company_code = '{회사코드}' AND menu_name_kor LIKE '%물류%';
|
||||
|
||||
INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, seq, menu_url, screen_code, company_code, status)
|
||||
VALUES (
|
||||
(SELECT COALESCE(MAX(objid), 0) + 1 FROM menu_info),
|
||||
2, -- 2 = 메뉴 항목
|
||||
{부모_objid}, -- 상위 메뉴의 objid
|
||||
'포장/적재정보',
|
||||
10, -- 정렬 순서
|
||||
'/screen/COMPANY_7_PKG', -- /screen/{screen_code} 형식 (절대!)
|
||||
'COMPANY_7_PKG', -- screen_definitions.screen_code와 일치
|
||||
'COMPANY_7',
|
||||
'Y'
|
||||
);
|
||||
```
|
||||
|
||||
**핵심**: `menu_url`은 반드시 `/screen/{screen_code}` 형식이어야 한다!
|
||||
프론트엔드가 이 URL을 받아 `screen_definitions`에서 screen_id를 찾고, `screen_layouts_v2`에서 레이아웃을 로드한다.
|
||||
|
||||
## 2. 관리자 메뉴 등록 (코드 구현 후 필수!)
|
||||
|
||||
|
|
@ -35,11 +116,14 @@
|
|||
|
||||
```sql
|
||||
-- 예시: 결재 템플릿 관리 메뉴 등록
|
||||
INSERT INTO menu_info (menu_id, menu_name, url, parent_id, menu_type, sort_order, is_active, company_code)
|
||||
VALUES ('approvalTemplate', '결재 템플릿', '/admin/approvalTemplate', 'approval', 'ADMIN', 40, 'Y', '대상회사코드');
|
||||
INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, seq, menu_url, company_code, status)
|
||||
VALUES (
|
||||
(SELECT COALESCE(MAX(objid), 0) + 1 FROM menu_info),
|
||||
2, {부모_objid}, '결재 템플릿', 40, '/admin/approvalTemplate', '대상회사코드', 'Y'
|
||||
);
|
||||
```
|
||||
|
||||
- 기존 메뉴 구조를 먼저 조회해서 parent_id, sort_order 등을 맞춰라
|
||||
- 기존 메뉴 구조를 먼저 조회해서 parent_obj_id, seq 등을 맞춰라
|
||||
- company_code 별로 등록이 필요할 수 있다
|
||||
- menu_auth_group 권한 매핑도 필요하면 추가
|
||||
|
||||
|
|
@ -63,15 +147,25 @@ VALUES ('approvalTemplate', '결재 템플릿', '/admin/approvalTemplate', 'appr
|
|||
|
||||
기능 하나를 "완성"이라고 말하려면 아래를 전부 충족해야 한다:
|
||||
|
||||
### 공통
|
||||
- [ ] DB: 마이그레이션 작성 + 실행 완료
|
||||
- [ ] DB: company_code 컬럼 + 인덱스 존재
|
||||
- [ ] BE: API 엔드포인트 구현 + 라우트 등록
|
||||
- [ ] BE: API 엔드포인트 구현 + 라우트 등록 (app.ts에 import + use 추가!)
|
||||
- [ ] BE: company_code 필터링 적용
|
||||
- [ ] FE: API 클라이언트 함수 작성 (lib/api/)
|
||||
- [ ] FE: 화면 컴포넌트 구현
|
||||
- [ ] **메뉴 등록**: 관리자 메뉴면 menu_info INSERT, 사용자 메뉴면 스크린 레이아웃 등록
|
||||
- [ ] 빌드 통과: 백엔드 tsc + 프론트엔드 tsc
|
||||
|
||||
### 관리자 메뉴인 경우
|
||||
- [ ] FE: `frontend/app/(main)/admin/{기능}/page.tsx` 작성
|
||||
- [ ] FE: API 클라이언트 함수 작성 (lib/api/)
|
||||
- [ ] DB: `menu_info` INSERT (menu_url = `/admin/{기능}`)
|
||||
|
||||
### 사용자 메뉴인 경우 (코드 작성 금지!)
|
||||
- [ ] DB: `screen_definitions` INSERT (screen_code, table_name, company_code)
|
||||
- [ ] DB: `screen_layouts_v2` INSERT (V2 레이아웃 JSON)
|
||||
- [ ] DB: `menu_info` INSERT (menu_url = `/screen/{screen_code}`)
|
||||
- [ ] BE: 필요한 경우 전용 API 작성 (범용 table-management API로 커버 안 되는 경우만)
|
||||
- [ ] FE: .tsx 페이지 파일 만들지 않음 (이미 `/screens/[screenId]/page.tsx`가 렌더링)
|
||||
|
||||
## 6. 절대 하지 말 것
|
||||
|
||||
1. 페이지 파일만 만들고 메뉴 등록 안 하기 (미완성!)
|
||||
|
|
@ -80,3 +174,9 @@ VALUES ('approvalTemplate', '결재 템플릿', '/admin/approvalTemplate', 'appr
|
|||
4. 하드코딩 색상/URL/사용자ID 사용
|
||||
5. Card 안에 Card 중첩 (중첩 박스 금지)
|
||||
6. 백엔드 재실행하기 (nodemon이 자동 재시작)
|
||||
7. **사용자 메뉴를 React 하드코딩(.tsx)으로 만들기 (절대 금지!!!)**
|
||||
- `frontend/app/(main)/production/`, `frontend/app/(main)/warehouse/` 등에 page.tsx 파일을 만들어서 직접 UI를 코딩하는 것은 절대 금지
|
||||
- 사용자 메뉴는 반드시 `screen_definitions` + `screen_layouts_v2` + `menu_info` DB 등록 방식으로 구현
|
||||
- 이미 `/screen/[screenCode]` → `/screens/[screenId]` 렌더링 시스템이 존재함
|
||||
- 백엔드 API(controller/routes)와 프론트엔드 API 클라이언트(lib/api/)는 필요하면 코드로 작성 가능
|
||||
- 하지만 프론트엔드 화면 UI 자체는 DB의 V2 레이아웃 JSON으로만 구성
|
||||
|
|
|
|||
|
|
@ -49,6 +49,35 @@ export async function getYourData(id: number) {
|
|||
}
|
||||
```
|
||||
|
||||
# CRITICAL: 사용자 메뉴 화면은 코드로 만들지 않는다!!!
|
||||
|
||||
**이 프로젝트는 로우코드 스크린 디자이너 시스템을 사용한다.**
|
||||
사용자 업무 화면(포장관리, 금형관리, BOM, 문서관리 등)은 절대 React 페이지(.tsx)로 직접 UI를 하드코딩하지 않는다!
|
||||
|
||||
## 금지 패턴 (절대 하지 말 것)
|
||||
```
|
||||
frontend/app/(main)/production/packaging/page.tsx ← 이런 파일 만들지 마라!
|
||||
frontend/app/(main)/warehouse/something/page.tsx ← 이런 파일 만들지 마라!
|
||||
```
|
||||
|
||||
## 올바른 패턴
|
||||
사용자 화면은 DB에 등록만 하면 자동으로 렌더링된다:
|
||||
1. `screen_definitions` 테이블에 화면 등록 (screen_code, table_name 등)
|
||||
2. `screen_layouts_v2` 테이블에 V2 레이아웃 JSON 등록 (v2-split-panel-layout, v2-table-list 등)
|
||||
3. `menu_info` 테이블에 메뉴 등록 (menu_url = `/screen/{screen_code}`)
|
||||
|
||||
이미 존재하는 렌더링 시스템:
|
||||
- `/screen/[screenCode]/page.tsx` → screenCode를 screenId로 변환
|
||||
- `/screens/[screenId]/page.tsx` → screen_layouts_v2에서 V2 레이아웃 로드 → DynamicComponentRenderer로 렌더링
|
||||
|
||||
## 프론트엔드 에이전트가 할 수 있는 것
|
||||
- `frontend/lib/api/` 하위에 API 클라이언트 함수 작성 (백엔드와 통신)
|
||||
- V2 컴포넌트 자체를 수정/신규 개발 (`frontend/lib/registry/components/v2-*/`)
|
||||
- 관리자 메뉴(`/admin/*`)는 React 페이지 코딩 가능
|
||||
|
||||
## 프론트엔드 에이전트가 할 수 없는 것
|
||||
- 사용자 메뉴 화면을 React 페이지로 직접 코딩하는 것
|
||||
|
||||
# Your Domain
|
||||
- frontend/components/
|
||||
- frontend/app/
|
||||
|
|
|
|||
|
|
@ -39,9 +39,23 @@ FORBIDDEN: bg-gray-50, text-blue-500, bg-white, text-black
|
|||
- Use cn() for conditional classes
|
||||
- Use lucide-react for ALL icons
|
||||
|
||||
# CRITICAL: 사용자 메뉴 화면은 코드로 만들지 않는다!!!
|
||||
|
||||
사용자 업무 화면(포장관리, 금형관리, BOM 등)의 UI는 DB의 `screen_layouts_v2` 테이블에 V2 레이아웃 JSON으로 정의된다.
|
||||
React 페이지(.tsx)로 직접 UI를 하드코딩하는 것은 절대 금지!
|
||||
|
||||
UI 에이전트가 할 수 있는 것:
|
||||
- V2 컴포넌트 자체의 스타일/UX 개선 (`frontend/lib/registry/components/v2-*/`)
|
||||
- 관리자 메뉴(`/admin/*`) 페이지의 UI 개선
|
||||
- 공통 UI 컴포넌트(`frontend/components/ui/`) 스타일 개선
|
||||
|
||||
UI 에이전트가 할 수 없는 것:
|
||||
- 사용자 메뉴 화면을 React 페이지로 직접 코딩
|
||||
|
||||
# Your Domain
|
||||
- frontend/components/ (UI components)
|
||||
- frontend/app/ (pages)
|
||||
- frontend/app/ (pages - 관리자 메뉴만)
|
||||
- frontend/lib/registry/components/v2-*/ (V2 컴포넌트)
|
||||
|
||||
# Output Rules
|
||||
1. TypeScript strict mode
|
||||
|
|
|
|||
35
.cursorrules
35
.cursorrules
|
|
@ -1541,3 +1541,38 @@ const query = `
|
|||
- 기본 5개 컬럼 누락 금지
|
||||
- 메타데이터 테이블 미등록 금지
|
||||
|
||||
---
|
||||
|
||||
## 화면 개발 방식 필수 규칙 (사용자 메뉴 vs 관리자 메뉴)
|
||||
|
||||
**상세 가이드**: [pipeline-common-rules.md](.cursor/agents/pipeline-common-rules.md)
|
||||
|
||||
### 핵심 원칙 (절대 위반 금지)
|
||||
|
||||
1. **사용자 업무 화면은 React 코드(.tsx)로 직접 만들지 않는다!**
|
||||
- 포장관리, 금형관리, BOM, 입출고, 품질 등 일반 업무 화면
|
||||
- DB에 `screen_definitions` + `screen_layouts_v2` + `menu_info` 등록으로 구현
|
||||
- 이미 `/screen/[screenCode]` → `/screens/[screenId]` 렌더링 시스템이 존재
|
||||
- V2 컴포넌트(v2-split-panel-layout, v2-table-list, v2-repeater 등)로 레이아웃 구성
|
||||
|
||||
2. **관리자 메뉴만 React 코드로 작성 가능**
|
||||
- 사용자 관리, 권한 관리, 시스템 설정 등
|
||||
- `frontend/app/(main)/admin/{기능}/page.tsx`에 작성
|
||||
- `menu_info` 테이블에 메뉴 등록 필수
|
||||
|
||||
### 사용자 메뉴 구현 순서
|
||||
|
||||
```
|
||||
1. DB 테이블 생성 (비즈니스 데이터용)
|
||||
2. screen_definitions INSERT (screen_code, table_name)
|
||||
3. screen_layouts_v2 INSERT (V2 레이아웃 JSON)
|
||||
4. menu_info INSERT (menu_url = '/screen/{screen_code}')
|
||||
5. 필요하면 백엔드 전용 API 추가 (범용 API로 안 되는 경우만)
|
||||
```
|
||||
|
||||
### 금지 사항
|
||||
|
||||
- `frontend/app/(main)/production/*/page.tsx` 같은 사용자 화면 하드코딩 금지
|
||||
- `frontend/app/(main)/warehouse/*/page.tsx` 같은 사용자 화면 하드코딩 금지
|
||||
- 사용자 메뉴의 UI를 React 컴포넌트로 직접 구현하는 것 금지
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ import taxInvoiceRoutes from "./routes/taxInvoiceRoutes"; // 세금계산서 관
|
|||
import cascadingRelationRoutes from "./routes/cascadingRelationRoutes"; // 연쇄 드롭다운 관계 관리
|
||||
import cascadingAutoFillRoutes from "./routes/cascadingAutoFillRoutes"; // 자동 입력 관리
|
||||
import cascadingConditionRoutes from "./routes/cascadingConditionRoutes"; // 조건부 연쇄 관리
|
||||
import packagingRoutes from "./routes/packagingRoutes"; // 포장/적재정보 관리
|
||||
import cascadingMutualExclusionRoutes from "./routes/cascadingMutualExclusionRoutes"; // 상호 배제 관리
|
||||
import cascadingHierarchyRoutes from "./routes/cascadingHierarchyRoutes"; // 다단계 계층 관리
|
||||
import categoryValueCascadingRoutes from "./routes/categoryValueCascadingRoutes"; // 카테고리 값 연쇄관계
|
||||
|
|
@ -321,6 +322,7 @@ app.use("/api/tax-invoice", taxInvoiceRoutes); // 세금계산서 관리
|
|||
app.use("/api/cascading-relations", cascadingRelationRoutes); // 연쇄 드롭다운 관계 관리
|
||||
app.use("/api/cascading-auto-fill", cascadingAutoFillRoutes); // 자동 입력 관리
|
||||
app.use("/api/cascading-conditions", cascadingConditionRoutes); // 조건부 연쇄 관리
|
||||
app.use("/api/packaging", packagingRoutes); // 포장/적재정보 관리
|
||||
app.use("/api/cascading-exclusions", cascadingMutualExclusionRoutes); // 상호 배제 관리
|
||||
app.use("/api/cascading-hierarchy", cascadingHierarchyRoutes); // 다단계 계층 관리
|
||||
app.use("/api/category-value-cascading", categoryValueCascadingRoutes); // 카테고리 값 연쇄관계
|
||||
|
|
|
|||
|
|
@ -266,7 +266,6 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re
|
|||
logger.info("컬럼 DISTINCT 값 조회 성공", {
|
||||
tableName,
|
||||
columnName,
|
||||
columnInputType: columnInputType || "none",
|
||||
labelColumn: effectiveLabelColumn,
|
||||
companyCode,
|
||||
hasFilters: !!filtersParam,
|
||||
|
|
|
|||
Loading…
Reference in New Issue