@blastlabs/utils 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,9 @@ const path = require("path");
7
7
  const RULE_CATEGORIES = {
8
8
  // 기본 규칙 (항상 포함)
9
9
  base: [
10
+ "development-workflow.md",
10
11
  "typescript-standards.md",
12
+ "naming-convention.md",
11
13
  "react-hooks.md",
12
14
  "testing.md",
13
15
  "documentation.md",
@@ -29,6 +31,7 @@ function parseArgs(args) {
29
31
  all: false,
30
32
  list: false,
31
33
  help: false,
34
+ force: false,
32
35
  };
33
36
 
34
37
  for (const arg of args) {
@@ -40,6 +43,8 @@ function parseArgs(args) {
40
43
  options.all = true;
41
44
  } else if (arg === "--list" || arg === "-l") {
42
45
  options.list = true;
46
+ } else if (arg === "--force" || arg === "-f") {
47
+ options.force = true;
43
48
  }
44
49
  }
45
50
 
@@ -53,12 +58,14 @@ function showHelp() {
53
58
  console.log("Options:");
54
59
  console.log(" --fsd FSD 아키텍처 규칙 포함");
55
60
  console.log(" --all 모든 규칙 설치 (base + fsd)");
61
+ console.log(" --force, -f 기존 파일 덮어쓰기");
56
62
  console.log(" --list, -l 설치 가능한 규칙 목록 보기");
57
63
  console.log(" --help, -h 도움말 보기");
58
64
  console.log("\n예시:");
59
65
  console.log(" npx blastlabs-init-ai-rules # 기본 규칙만");
60
66
  console.log(" npx blastlabs-init-ai-rules --fsd # 기본 + FSD 규칙");
61
67
  console.log(" npx blastlabs-init-ai-rules --all # 모든 규칙");
68
+ console.log(" npx blastlabs-init-ai-rules --force # 기존 파일 덮어쓰기");
62
69
  console.log("\n생성되는 파일:");
63
70
  console.log(" .cursor/rules/*.mdc - Cursor용 규칙 파일들");
64
71
  console.log(" .claude/rules/*.md - Claude Code용 규칙 파일들");
@@ -176,7 +183,7 @@ function serializeClaudeFrontmatter(frontmatter) {
176
183
  return yaml;
177
184
  }
178
185
 
179
- function generateCursorRules(ruleFiles, sourceDir, targetDir) {
186
+ function generateCursorRules(ruleFiles, sourceDir, targetDir, force = false) {
180
187
  const cursorDir = path.join(targetDir, ".cursor", "rules");
181
188
  if (!fs.existsSync(cursorDir)) {
182
189
  fs.mkdirSync(cursorDir, { recursive: true });
@@ -197,7 +204,7 @@ function generateCursorRules(ruleFiles, sourceDir, targetDir) {
197
204
  continue;
198
205
  }
199
206
 
200
- if (fs.existsSync(targetPath)) {
207
+ if (fs.existsSync(targetPath) && !force) {
201
208
  console.log(` ⏭️ ${targetName} - 이미 존재함, 건너뜀`);
202
209
  skipped++;
203
210
  continue;
@@ -208,14 +215,15 @@ function generateCursorRules(ruleFiles, sourceDir, targetDir) {
208
215
  const output = serializeCursorFrontmatter(frontmatter) + body;
209
216
 
210
217
  fs.writeFileSync(targetPath, output);
211
- console.log(` ${targetName}`);
218
+ const action = force && fs.existsSync(targetPath) ? "🔄" : "";
219
+ console.log(` ${action} ${targetName}`);
212
220
  installed++;
213
221
  }
214
222
 
215
223
  return { installed, skipped };
216
224
  }
217
225
 
218
- function generateClaudeRules(ruleFiles, sourceDir, targetDir) {
226
+ function generateClaudeRules(ruleFiles, sourceDir, targetDir, force = false) {
219
227
  const claudeDir = path.join(targetDir, ".claude", "rules");
220
228
  if (!fs.existsSync(claudeDir)) {
221
229
  fs.mkdirSync(claudeDir, { recursive: true });
@@ -235,7 +243,7 @@ function generateClaudeRules(ruleFiles, sourceDir, targetDir) {
235
243
  continue;
236
244
  }
237
245
 
238
- if (fs.existsSync(targetPath)) {
246
+ if (fs.existsSync(targetPath) && !force) {
239
247
  console.log(` ⏭️ ${ruleName} - 이미 존재함, 건너뜀`);
240
248
  skipped++;
241
249
  continue;
@@ -246,7 +254,8 @@ function generateClaudeRules(ruleFiles, sourceDir, targetDir) {
246
254
  const output = serializeClaudeFrontmatter(frontmatter) + body;
247
255
 
248
256
  fs.writeFileSync(targetPath, output);
249
- console.log(` ${ruleName}`);
257
+ const action = force && fs.existsSync(targetPath) ? "🔄" : "";
258
+ console.log(` ${action} ${ruleName}`);
250
259
  installed++;
251
260
  }
252
261
 
@@ -291,11 +300,11 @@ function main() {
291
300
 
292
301
  // Cursor 규칙 생성
293
302
  console.log("🔧 Cursor 규칙 생성:\n");
294
- const cursor = generateCursorRules(rulesToInstall, rulesSourceDir, targetDir);
303
+ const cursor = generateCursorRules(rulesToInstall, rulesSourceDir, targetDir, options.force);
295
304
 
296
305
  // Claude 규칙 생성
297
306
  console.log("\n🤖 Claude 규칙 생성:\n");
298
- const claude = generateClaudeRules(rulesToInstall, rulesSourceDir, targetDir);
307
+ const claude = generateClaudeRules(rulesToInstall, rulesSourceDir, targetDir, options.force);
299
308
 
300
309
  const totalInstalled = cursor.installed + claude.installed;
301
310
  const totalSkipped = cursor.skipped + claude.skipped;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blastlabs/utils",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,96 @@
1
+ ---
2
+ description: "개발 워크플로우: PRD 참고 및 테스트 기본 진행"
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Development Workflow
7
+
8
+ ## 필수 원칙
9
+
10
+ ### 1. PRD 먼저 확인
11
+
12
+ 모든 기능 개발 전에 **반드시** PRD(Product Requirement Document)를 먼저 확인하세요:
13
+
14
+ - **PRD가 있다면** 내용을 확인하고 스펙 파악
15
+ - **PRD가 없다면** 사용자와 함께 PRD를 작성
16
+ - 사용자로부터 요구사항 수집
17
+ - AI가 PRD 초안 작성
18
+ - 사용자 확인 후 개발 진행
19
+ - PRD에서 요구사항, UI/UX 스펙, 비즈니스 로직을 명확히 파악
20
+ - 불확실한 부분은 PRD를 통해 명확화 후 개발 진행
21
+
22
+ **PRD 확인 체크리스트:**
23
+ - [ ] 기능 목적과 목표가 명확한가?
24
+ - [ ] UI/UX 스펙이 정의되어 있는가?
25
+ - [ ] API 스펙이 정의되어 있는가?
26
+ - [ ] 예외 상황과 에러 처리가 정의되어 있는가?
27
+
28
+ ### 2. 테스트 기본 진행
29
+
30
+ **모든 기능 구현은 테스트 코드와 함께 진행합니다.**
31
+
32
+ #### 테스트 우선 원칙
33
+
34
+ 1. **테스트 없이 구현하지 않기**
35
+ - 기능 구현 전 테스트 케이스 먼저 작성
36
+ - 테스트가 실패하면 구현으로 간주하지 않음
37
+
38
+ 2. **최소 테스트 커버리지 준수**
39
+ - 새로운 hook: 100% 커버리지 권장
40
+ - 컴포넌트: 주요 사용자 시나리오 테스트
41
+ - 유틸리티 함수: 100% 커버리지
42
+
43
+ 3. **테스트 실행 후 PR**
44
+ - `npm test` 통과 확인
45
+ - 빌드 성공 확인
46
+ - 테스트 실패 시 PR 불가
47
+
48
+ ## 개발 순서
49
+
50
+ ### Step 1: PRD 확인/작성
51
+ ```
52
+ 사용자: "로그인 기능 추가해줘"
53
+ AI: "PRD가 있나요? 없다면 먼저 함께 작성해요.
54
+
55
+ 먼저 몇 가지 질문드릴게요:
56
+ 1. 로그인 방법 (이메일/소셜)
57
+ 2. 필요한 UI 컴포넌트
58
+ 3. 연동할 API 엔드포인트
59
+
60
+ 이를 바탕으로 PRD 초안을 작성할게요."
61
+ ```
62
+
63
+ ### Step 2: 테스트 코드 작성
64
+ ```
65
+ AI: "테스트 코드부터 작성할게요."
66
+ → 로그인 테스트 작성
67
+ → 실패하는 것 확인
68
+ ```
69
+
70
+ ### Step 3: 기능 구현
71
+ ```
72
+ AI: "이제 로그인 기능을 구현할게요."
73
+ → 최소한의 구현으로 테스트 통과
74
+ ```
75
+
76
+ ### Step 4: 리팩토링
77
+ ```
78
+ AI: "코드를 정리할게요."
79
+ → 리팩토링 후 테스트 재실행
80
+ → 모든 테스트 통과 확인
81
+ ```
82
+
83
+ ## 예외 사항
84
+
85
+ **테스트를 생략할 수 있는 경우:**
86
+ - 프로토타이핑 (코드 폐기 예정)
87
+ - 스타일만 변경하는 경우
88
+ - 명시적으로 테스트 제외 요청 시
89
+
90
+ **그 외에는 무조건 테스트 작성**
91
+
92
+ ## 중요
93
+
94
+ - **Always** check PRD first before implementation
95
+ - **Always** write tests before or with implementation
96
+ - **Never** skip tests without explicit reason
@@ -1,6 +1,8 @@
1
1
  ---
2
- description: "문서화 가이드라인"
2
+ description: '문서화 가이드라인'
3
3
  alwaysApply: false
4
+ paths:
5
+ - '**/docs/**'
4
6
  ---
5
7
 
6
8
  # Documentation Guidelines
@@ -14,7 +16,7 @@ alwaysApply: false
14
16
  ## 문서 업데이트
15
17
 
16
18
  - 기능 추가 시 README.md 업데이트
17
- - 새 hooks 추가 시 관련 docs/*.md 업데이트
19
+ - 새 hooks 추가 시 관련 docs/\*.md 업데이트
18
20
  - 변경사항은 반드시 CHANGELOG.md에 기록
19
21
 
20
22
  ## CHANGELOG 작성
@@ -1,14 +1,17 @@
1
1
  ---
2
- description: "Entities 레이어 구조 및 API 패턴"
2
+ description: 'Entities 레이어 구조 및 API 패턴'
3
3
  paths:
4
- - "**/entities/**"
4
+ - '**/entities/**'
5
5
  ---
6
6
 
7
7
  # Entities Layer (엔티티)
8
8
 
9
9
  엔티티는 도메인 모델과 API 로직을 관리합니다.
10
+ mapper는 받은데이터를 model에 설계한 방식으로 map을 합니다.
11
+ query는 params로 보통 get에서 사용합니다.
10
12
 
11
13
  ## 구조
14
+
12
15
  ```
13
16
  entities/
14
17
  └── (엔티티 이름)/ 예: inquiry, popular-product
@@ -22,29 +25,27 @@ entities/
22
25
  │ ├── get-(엔티티 이름)-list.ts
23
26
  │ ├── get-(엔티티 이름)-detail.ts
24
27
  │ ├── (엔티티 이름)-queries.ts
25
- │ ├── mutate.ts
26
- │ ├── query.ts
27
28
  │ └── index.ts
28
29
  ├── model/
29
30
  │ ├── (엔티티 이름).ts
30
31
  │ ├── (엔티티 이름)-detail.ts
31
32
  │ └── (엔티티 이름)-update.ts
32
- ├── schema.ts
33
33
  └── index.ts
34
34
  ```
35
35
 
36
36
  ## 각 파일의 역할
37
+
37
38
  - **mapper/**: API 응답을 도메인 모델로 변환
38
39
  - **query/**: 쿼리 파라미터 타입 정의
39
- - **get-*-list.ts**: 리스트 조회 API 함수
40
- - **get-*-detail.ts**: 상세 조회 API 함수
40
+ - **get-\*-list.ts**: 리스트 조회 API 함수
41
+ - **get-\*-detail.ts**: 상세 조회 API 함수
42
+ - **post-\*.ts** : post API 함수 (delete, put같은것도 앞에 접두사가 변함)
41
43
  - **\*-queries.ts**: TanStack Query queryOptions 정의 (all, lists, list, details, detail 등)
42
- - **mutate.ts**: mutation 함수들
43
44
  - **model/**: 도메인 모델 타입 정의
44
- - **schema.ts**: zod 스키마 정의 및 변환 함수
45
45
  - **index.ts**: `export * as (엔티티 이름)Api from "./api"`
46
46
 
47
47
  ## 예시
48
+
48
49
  ```
49
50
  entities/inquiry/
50
51
  ├── api/
@@ -60,8 +61,12 @@ entities/inquiry/
60
61
  ├── model/
61
62
  │ ├── inquiry.ts
62
63
  │ └── inquiry-detail.ts
63
- └── schema.ts
64
64
  ```
65
65
 
66
66
  ## TanStack Query 패턴
67
+
67
68
  서버 상태 관리는 TanStack Query의 queryOptions 패턴을 사용합니다.
69
+
70
+ ## model의 규칙
71
+
72
+ zod를 사용하며 export const (엔티티)Schema와 export type (엔티티)Type 의 네이밍 규칙을 활용한다
@@ -0,0 +1,194 @@
1
+ ---
2
+ description: "파일 및 변수 네이밍 컨벤션"
3
+ alwaysApply: false
4
+ paths:
5
+ - "**/*.ts"
6
+ - "**/*.tsx"
7
+ - "**/*.js"
8
+ - "**/*.jsx"
9
+ ---
10
+
11
+ # Naming Convention
12
+
13
+ ## 파일 네이밍
14
+
15
+ ### 기본 규칙
16
+
17
+ **모든 파일/폴더는 `kebab-case` 사용**
18
+
19
+ ```
20
+ ✅ good
21
+ user-profile/
22
+ use-user-data/
23
+ button-group.tsx
24
+ user-card.tsx
25
+ login-form.tsx
26
+
27
+ ❌ bad
28
+ UserProfile/
29
+ useUserData/
30
+ buttonGroup.tsx
31
+ UserCard.tsx
32
+ LoginForm.tsx
33
+ ```
34
+
35
+ ### Hooks
36
+
37
+ **`use-` prefix + `camelCase`**
38
+
39
+ ```
40
+ hooks/
41
+ ✅ use-user-data.ts
42
+ ✅ use-form-state.ts
43
+ ✅ use-debounce.ts
44
+
45
+ ❌ userData.ts
46
+ ❌ useUserData.ts (파일명)
47
+ ❌ form-state.ts
48
+ ```
49
+
50
+ ### 타입 파일
51
+
52
+ **`.types.ts` 또는 `.type.ts` 접미사**
53
+
54
+ ```
55
+ model/
56
+ ✅ user.types.ts
57
+ ✅ product.type.ts
58
+ ✅ api.types.ts
59
+
60
+ ❌ UserTypes.ts
61
+ ❷ user-type.ts (단수型)
62
+ ```
63
+
64
+ ### 테스트 파일
65
+
66
+ **대상 파일명 + `.test.` + 확장자**
67
+
68
+ ```
69
+ button-group.tsx → button-group.test.tsx
70
+ use-user-data.ts → use-user-data.test.ts
71
+ api.ts → api.test.ts
72
+ ```
73
+
74
+ ### 폴더 구조
75
+
76
+ **FSD 아키텍처 예시**
77
+
78
+ ```
79
+ src/
80
+ ├── entities/
81
+ │ └── user/ # kebab-case
82
+ │ ├── model/
83
+ │ │ ├── user.types.ts
84
+ │ │ └── user-detail.types.ts
85
+ │ ├── api/
86
+ │ │ ├── get-user-list.ts
87
+ │ │ ├── use-user-query.ts
88
+ │ │ └── user.test.ts
89
+ │ └── ui/
90
+ │ ├── user-card.tsx
91
+ │ └── user-card.test.tsx
92
+ ├── features/
93
+ │ └── auth-login/ # kebab-case
94
+ │ ├── model/
95
+ │ ├── api/
96
+ │ └── ui/
97
+ │ ├── login-form.tsx
98
+ │ └── login-form.test.tsx
99
+ ├── shared/
100
+ │ └── ui/
101
+ │ └── button/ # kebab-case
102
+ │ ├── button.tsx
103
+ │ └── button.test.tsx
104
+ ```
105
+
106
+ ## 변수/함수 네이밍
107
+
108
+ ### 변수
109
+
110
+ **`camelCase` 사용**
111
+
112
+ ```typescript
113
+ ✅ good
114
+ const userName = '...';
115
+ const isActive = true;
116
+ const maxCount = 10;
117
+
118
+ ❌ bad
119
+ const user_name = '...';
120
+ const IsActive = true;
121
+ const MAX_COUNT = 10; (const가 아니라면)
122
+ ```
123
+
124
+ ### 상수
125
+
126
+ **`UPPER_SNAKE_CASE` 사용**
127
+
128
+ ```typescript
129
+ ✅ good
130
+ const MAX_RETRY_COUNT = 3;
131
+ const API_BASE_URL = 'https://...';
132
+ const DEFAULT_TIMEOUT = 5000;
133
+
134
+ ❌ bad
135
+ const maxRetryCount = 3;
136
+ const apiBaseUrl = 'https://...';
137
+ ```
138
+
139
+ ### 함수
140
+
141
+ **`camelCase` + 동사로 시작**
142
+
143
+ ```typescript
144
+ ✅ good
145
+ function getUserData() { }
146
+ function handleSubmit() { }
147
+ function isValidEmail() { }
148
+
149
+ ❌ bad
150
+ function userData() { }
151
+ function form_submit() { }
152
+ function ValidEmail() { }
153
+ ```
154
+
155
+ ### 인터페이스/타입
156
+
157
+ **`PascalCase` 사용**
158
+
159
+ ```typescript
160
+ ✅ good
161
+ interface UserProfile { }
162
+ type LoginFormData = { };
163
+ interface ApiResponse<T> { }
164
+
165
+ ❌ bad
166
+ interface userProfile { }
167
+ type loginFormData = { }
168
+ ```
169
+
170
+ ### Enum
171
+
172
+ **`PascalCase` + 멤버는 `UPPER_SNAKE_CASE`**
173
+
174
+ ```typescript
175
+ ✅ good
176
+ enum UserRole {
177
+ ADMIN = 'ADMIN',
178
+ USER = 'USER',
179
+ GUEST = 'GUEST',
180
+ }
181
+
182
+ ❌ bad
183
+ enum userRole {
184
+ admin = 'admin',
185
+ USER = 'user',
186
+ }
187
+ ```
188
+
189
+ ## 중요
190
+
191
+ - **Always** use kebab-case for all files and folders (including components)
192
+ - **Always** use camelCase for variables and functions
193
+ - **Always** use UPPER_SNAKE_CASE for constants
194
+ - **Always** use PascalCase for types, interfaces, enums, and React component exports
@@ -1,7 +1,7 @@
1
1
  ---
2
- description: "Shared 레이어 구조 및 lib vs utils 구분"
2
+ description: 'Shared 레이어 구조 및 lib vs utils 구분'
3
3
  paths:
4
- - "**/shared/**"
4
+ - '**/shared/**'
5
5
  ---
6
6
 
7
7
  # Shared Layer (공유 레이어)
@@ -9,11 +9,10 @@ paths:
9
9
  프로젝트 전체에서 공유되는 코드를 관리합니다.
10
10
 
11
11
  ## 구조
12
+
12
13
  ```
13
14
  shared/
14
15
  ├── api/ API 인스턴스 설정 (instance_v2.ts 등)
15
- ├── constant/ 공통 상수 (path.ts 등)
16
- ├── hooks/ 공통 React 훅
17
16
  ├── mock-data/ 개발용 목 데이터
18
17
  ├── lib/ 라이브러리 래퍼 및 설정
19
18
  │ └── (라이브러리명)/
@@ -28,14 +27,15 @@ shared/
28
27
  │ │ └── (컴포넌트명).tsx
29
28
  │ ├── theme/ 테마 관련 CSS 파일
30
29
  │ └── utils/ UI 관련 유틸리티 함수
31
- ├── util/ 일반 유틸리티 함수
32
30
  └── utils/ 추가 유틸리티 함수
33
31
  ```
34
32
 
35
33
  ## lib vs utils 구분 기준
36
34
 
37
35
  ### `shared/lib/`
36
+
38
37
  외부 라이브러리를 프로젝트에 맞게 감싸거나 설정하는 코드
38
+
39
39
  - 라이브러리 래퍼(wrapper) 컴포넌트
40
40
  - 라이브러리 초기 설정 및 Provider
41
41
  - 예시:
@@ -43,16 +43,19 @@ shared/
43
43
  - `shared/lib/tanstack-query/query-client.tsx`
44
44
  - `shared/lib/suspense/suspense-wrapper.tsx`
45
45
 
46
- ### `shared/utils/` (또는 `shared/util/`)
46
+ ### `shared/utils/`
47
+
47
48
  순수 유틸리티 함수 및 비즈니스 로직 헬퍼
49
+
48
50
  - 라이브러리와 무관한 순수 함수
49
51
  - 라이브러리 기능을 사용하는 유틸 함수
50
52
  - 예시:
51
- - `shared/util/convert-price.ts` - 가격 변환 함수
52
- - `shared/util/export-excel.ts` - 엑셀 내보내기 함수
53
- - `shared/util/form-validation.ts` - 폼 검증 유틸
53
+ - `shared/utils/convert-price.ts` - 가격 변환 함수
54
+ - `shared/utils/export-excel.ts` - 엑셀 내보내기 함수
55
+ - `shared/utils/form-validation.ts` - 폼 검증 유틸
54
56
 
55
57
  ## 판단 기준
58
+
56
59
  - 라이브러리를 감싸는가? → `lib/`
57
60
  - 라이브러리와 무관한 로직인가? → `utils/`
58
61
  - 라이브러리 기능을 사용하는 유틸인가? → `utils/`
@@ -1,72 +1,34 @@
1
1
  ---
2
- description: "Views 레이어 (페이지) 구조"
2
+ description: 'Views 레이어 (페이지) 구조'
3
3
  paths:
4
- - "**/views/**"
4
+ - '**/views/**'
5
5
  ---
6
6
 
7
7
  # Views Layer (페이지 레이어)
8
8
 
9
9
  페이지 단위의 UI와 로직을 관리합니다.
10
+ 기본적으로 fsd 방식을 따릅니다
10
11
 
11
12
  ## 구조
13
+
12
14
  ```
13
15
  views/
14
16
  └── (페이지명)/ 예: inquiry, product, seller
15
17
  ├── page.tsx 메인 페이지 컴포넌트 (필수)
16
- ├── (페이지명)-list-page.tsx
17
- ├── (페이지명)-detail-page.tsx
18
18
  ├── detail/
19
19
  │ ├── page.tsx
20
20
  │ ├── ui/
21
- │ ├── hooks/ use-(페이지명)-detail-data.tsx 등
22
- │ └── components/
23
- ├── list/
24
- │ ├── (컬럼명)-column.tsx
25
- │ ├── search.tsx
26
- │ └── (모달명)-modal.tsx
27
21
  ├── ui/
28
- ├── hooks/ use-(페이지명)-data.tsx, use-(페이지명)-actions.tsx 등
29
- ├── components/
30
22
  ├── utils/
31
- ├── schema/ zod 스키마
32
- └── constants.ts
33
23
  ```
34
24
 
35
25
  ## 예시
36
26
 
37
27
  ### 기본 페이지 구조
28
+
38
29
  ```
39
30
  views/inquiry/
40
31
  ├── detail/
41
32
  │ └── page.tsx
42
- ├── inquiry-list-page.tsx
43
- └── list/
44
- ├── inquiry-column.tsx
45
- ├── inquiry-modal.tsx
46
- └── search.tsx
47
- ```
48
-
49
- ### 상세 페이지 구조
50
- ```
51
- views/ai-management/agency/agency-detail/
52
- ├── page.tsx
53
- ├── hooks/
54
- │ ├── use-agency-detail-data.tsx
55
- │ └── use-agency-data-actions.tsx
56
- ├── components/
57
- │ └── agency-basic-info-tab.tsx
58
- └── schema/
59
- └── agency-detail.ts
60
- ```
61
-
62
- ### 관리 페이지 구조
63
- ```
64
- views/product/category-management/
65
- ├── page.tsx
66
- ├── hooks/
67
- │ ├── use-category-data.tsx
68
- │ └── use-category-actions.tsx
69
- └── ui/
70
- ├── category-management.tsx
71
- └── category-modal.tsx
33
+ page.tsx
72
34
  ```