@farmzone/fz-template-react 1.0.4 → 1.0.5

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.
package/README.md CHANGED
@@ -24,68 +24,58 @@ npx @farmzone/fz-template-react
24
24
  # 프로젝트 이름을 입력하세요: my-app
25
25
  ```
26
26
 
27
- ## 생성되는 프로젝트 구조
28
-
29
- ```
30
- my-app/
31
- ├── src/
32
- │ ├── app/
33
- │ │ ├── layout/
34
- │ │ │ ├── Layout.tsx # 사이드바 + Outlet 레이아웃
35
- │ │ │ ├── Sidebar.tsx # 사이드바 컴포넌트
36
- │ │ │ └── menu.ts # 메뉴 설정
37
- │ │ ├── router/
38
- │ │ │ └── Router.tsx
39
- │ │ └── App.tsx # QueryClientProvider 루트
40
- │ ├── pages/
41
- │ │ ├── dashboard/
42
- │ │ │ └── index.tsx
43
- │ │ └── error/
44
- │ │ ├── Error.tsx
45
- │ │ └── NotFound.tsx
46
- │ └── index.tsx
47
- ├── .gitignore
48
- ├── index.html
49
- ├── index.css
50
- ├── vite.config.ts
51
- ├── tsconfig.json
52
- └── package.json
53
- ```
54
-
55
27
  ## 포함된 기술 스택
56
28
 
57
- | 항목 | 버전 |
58
- | --------------------- | ------ |
59
- | React | ^19 |
60
- | Vite | ^7 |
61
- | TypeScript | ~5.9 |
62
- | Tailwind CSS | v4 |
63
- | React Router | ^7 |
64
- | TanStack Query | ^5 |
65
- | @farmzone/fz-react-ui | latest |
66
- | react-hook-form + zod | 최신 |
29
+ | 항목 | 버전 |
30
+ | --------------------- | ------- |
31
+ | React | ^19 |
32
+ | Vite | ^7 |
33
+ | TypeScript | ~5.9 |
34
+ | Tailwind CSS | ^4 |
35
+ | React Router | ^7 |
36
+ | TanStack Query | ^5 |
37
+ | @farmzone/fz-react-ui | ^0.0.6 |
38
+ | react-hook-form | ^7 |
39
+ | zod | ^3 |
40
+ | Zustand | ^5 |
41
+ | axios | ^1 |
42
+ | dayjs | ^1 |
43
+ | MSW | ^2 |
44
+ | lucide-react | ^1 |
67
45
 
68
46
  ## 메뉴 추가
69
47
 
70
- `src/app/layout/menu.ts` 수정:
48
+ `src/app/layout/menu.ts`의 `MENU_SECTIONS` 배열에 추가:
71
49
 
72
50
  ```ts
73
- import { LayoutDashboard, Users, ScrollText } from "lucide-react";
51
+ import type { MenuSection } from "@farmzone/fz-react-ui";
52
+ import { LayoutDashboard, Users, FileText, ScrollText } from "lucide-react";
74
53
 
75
- export const MENU_SECTIONS: MenuSection[] = [
76
- {
77
- items: [{ icon: LayoutDashboard, label: "대시보드", path: "/" }],
78
- },
54
+ export const MENU_SECTIONS: Array<MenuSection> = [
79
55
  {
80
- title: "관리",
81
56
  items: [
82
- { icon: Users, label: "사용자 관리", path: "/user" },
83
- { icon: ScrollText, label: "로그", path: "/log" },
57
+ { icon: LayoutDashboard, label: "대시보드", path: "/" },
58
+ {
59
+ icon: FileText,
60
+ label: "게시글 관리",
61
+ children: [{ label: "게시글 관리", path: "/post" }],
62
+ },
63
+ {
64
+ icon: Users,
65
+ label: "사용자 관리",
66
+ children: [{ label: "사용자 관리", path: "/user" }],
67
+ },
84
68
  ],
85
69
  },
70
+ {
71
+ title: "시스템",
72
+ items: [{ icon: ScrollText, label: "로그 관리", path: "/system/log" }],
73
+ },
86
74
  ];
87
75
  ```
88
76
 
77
+ `children` 배열을 사용하면 아코디언 서브메뉴로 렌더링됩니다.
78
+
89
79
  ## 라우트 추가
90
80
 
91
81
  `src/app/router/Router.tsx`의 `children` 배열에 추가:
@@ -93,12 +83,18 @@ export const MENU_SECTIONS: MenuSection[] = [
93
83
  ```tsx
94
84
  children: [
95
85
  { index: true, element: <DashboardPage /> },
96
- { path: "user", element: <UserListPage /> },
97
- { path: "user/:id", element: <UserDetailPage /> },
98
- { path: "log", element: <LogPage /> },
86
+ { path: "sample", element: <SamplePage /> },
87
+ { path: "sample/modal", element: <SampleModalPage /> },
88
+ { path: "sample/:id", element: <SampleDetailPage /> },
89
+ { path: "post", element: <PostPage /> },
90
+ { path: "post/:id", element: <PostDetailPage /> },
91
+ { path: "user", element: <UserPage /> },
92
+ { path: "system/log", element: <LogPage /> },
99
93
  ],
100
94
  ```
101
95
 
96
+ `/login` 경로와 `authLoader`(토큰 미존재 시 `/login` 리다이렉트)는 Router.tsx에 이미 포함되어 있습니다.
97
+
102
98
  ## npm 배포
103
99
 
104
100
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmzone/fz-template-react",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Farmzone React 프로젝트 보일러플레이트 생성 CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "__PROJECT_NAME__",
3
3
  "private": true,
4
- "version": "1.0.4",
4
+ "version": "1.0.5",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite --host 0.0.0.0 --port 5000",
@@ -10,7 +10,7 @@
10
10
  "preview": "vite preview"
11
11
  },
12
12
  "dependencies": {
13
- "@farmzone/fz-react-ui": "^0.0.6",
13
+ "@farmzone/fz-react-ui": "^0.0.8",
14
14
  "@hookform/resolvers": "^5.2.2",
15
15
  "@tanstack/react-query": "^5.90.0",
16
16
  "axios": "^1.13.0",
@@ -9,8 +9,8 @@ importers:
9
9
  .:
10
10
  dependencies:
11
11
  '@farmzone/fz-react-ui':
12
- specifier: ^0.0.6
13
- version: 0.0.6(@hookform/resolvers@5.4.0(react-hook-form@7.78.0(react@19.2.7)))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-daum-postcode@4.0.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react-hook-form@7.78.0(react@19.2.7))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(zod@3.25.76)
12
+ specifier: ^0.0.8
13
+ version: 0.0.8(@hookform/resolvers@5.4.0(react-hook-form@7.78.0(react@19.2.7)))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-daum-postcode@4.0.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react-hook-form@7.78.0(react@19.2.7))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(zod@3.25.76)
14
14
  '@hookform/resolvers':
15
15
  specifier: ^5.2.2
16
16
  version: 5.4.0(react-hook-form@7.78.0(react@19.2.7))
@@ -391,8 +391,8 @@ packages:
391
391
  resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
392
392
  engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
393
393
 
394
- '@farmzone/fz-react-ui@0.0.6':
395
- resolution: {integrity: sha512-4BwWCXQBtjZf9Aj1CmnbulJQHMtUVX5EmjhRpNCYbnovD4o+hz/TUTuv3y+JmNqZFnq5TYNgB6zzytBhBpIZ5g==}
394
+ '@farmzone/fz-react-ui@0.0.8':
395
+ resolution: {integrity: sha512-atqHgWVSy0sy15P30i1jN3BMtZJQbfyoMwUk1K5Xpgrk4xXtN8lC8AK+LMV8ZHfq0hV0EX+SHJGVpee0PLeHNQ==}
396
396
  peerDependencies:
397
397
  '@hookform/resolvers': ^5.2.2
398
398
  react: '>=18'
@@ -2501,7 +2501,7 @@ snapshots:
2501
2501
  '@eslint/core': 0.17.0
2502
2502
  levn: 0.4.1
2503
2503
 
2504
- '@farmzone/fz-react-ui@0.0.6(@hookform/resolvers@5.4.0(react-hook-form@7.78.0(react@19.2.7)))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-daum-postcode@4.0.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react-hook-form@7.78.0(react@19.2.7))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(zod@3.25.76)':
2504
+ '@farmzone/fz-react-ui@0.0.8(@hookform/resolvers@5.4.0(react-hook-form@7.78.0(react@19.2.7)))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-daum-postcode@4.0.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react-hook-form@7.78.0(react@19.2.7))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(zod@3.25.76)':
2505
2505
  dependencies:
2506
2506
  '@radix-ui/react-popover': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
2507
2507
  '@radix-ui/react-radio-group': 1.4.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
@@ -221,6 +221,11 @@ export const useDeleteComment = () => {
221
221
  });
222
222
  };
223
223
 
224
+ // --- Sample check ---
225
+
226
+ export const checkSampleNameAvailable = (name: string): Promise<boolean> =>
227
+ apiInstance.get<boolean>("/samples/check/name", { params: { name } }).then((r) => r.data);
228
+
224
229
  // --- User ---
225
230
 
226
231
  export const checkUserIdAvailable = (userId: string): Promise<boolean> =>
@@ -209,7 +209,6 @@ export default function PostPage() {
209
209
  data={content}
210
210
  isLoading={isLoading}
211
211
  rowKey="id"
212
- sortVisibleType={"hovered"}
213
212
  sortOption={sortOption}
214
213
  onSortChange={(sortKey: string, sortOrder: "asc" | "desc") => {
215
214
  setSortOption({ sortKey, sortOrder });
@@ -1,10 +1,15 @@
1
+ import { useEffect } from "react";
2
+ import { useFormContext } from "react-hook-form";
1
3
  import { Button, Modal, ModalBody, ModalFooter, ModalIconHeader, SubmitForm } from "@farmzone/fz-react-ui";
2
4
  import { z } from "zod";
3
5
 
6
+ import { checkSampleNameAvailable } from "@/app/api/queries";
7
+
4
8
  export const sampleFormSchema = z.object({
5
9
  name: z.string().min(1, "이름을 입력해 주세요."),
6
10
  description: z.string().min(1, "설명을 입력해 주세요."),
7
11
  category: z.string(),
12
+ priority: z.coerce.number().min(1).max(10),
8
13
  active: z.boolean(),
9
14
  });
10
15
 
@@ -14,6 +19,7 @@ export const SAMPLE_FORM_DEFAULT_VALUES: SampleFormData = {
14
19
  name: "",
15
20
  description: "",
16
21
  category: "BASIC",
22
+ priority: 1,
17
23
  active: true,
18
24
  };
19
25
 
@@ -22,6 +28,34 @@ const CATEGORY_OPTIONS = [
22
28
  { label: "고급", value: "ADVANCED" },
23
29
  ];
24
30
 
31
+ const PRIORITY_OPTIONS = Array.from({ length: 10 }, (_, i) => ({
32
+ label: String(i + 1),
33
+ value: String(i + 1),
34
+ }));
35
+
36
+ function SampleNameChecker({ originalName }: { originalName?: string }) {
37
+ const { watch, setError, clearErrors } = useFormContext();
38
+ const name = watch("name") as string;
39
+
40
+ useEffect(() => {
41
+ if (!name || name === originalName) {
42
+ clearErrors("name");
43
+ return;
44
+ }
45
+ const timer = setTimeout(async () => {
46
+ const available = await checkSampleNameAvailable(name);
47
+ if (available) {
48
+ clearErrors("name");
49
+ } else {
50
+ setError("name", { type: "manual", message: "이미 사용 중인 샘플명입니다." });
51
+ }
52
+ }, 500);
53
+ return () => clearTimeout(timer);
54
+ }, [name, originalName, setError, clearErrors]);
55
+
56
+ return null;
57
+ }
58
+
25
59
  interface SampleFormModalProps {
26
60
  mode: "create" | "edit";
27
61
  isOpen: boolean;
@@ -30,6 +64,7 @@ interface SampleFormModalProps {
30
64
  onSubmit: (data: SampleFormData) => Promise<void>;
31
65
  isPending: boolean;
32
66
  formKey?: string | number;
67
+ originalName?: string;
33
68
  }
34
69
 
35
70
  export function SampleFormModal({
@@ -40,6 +75,7 @@ export function SampleFormModal({
40
75
  onSubmit,
41
76
  isPending,
42
77
  formKey,
78
+ originalName,
43
79
  }: SampleFormModalProps) {
44
80
  const formId = mode === "create" ? "sample-create-form" : "sample-edit-form";
45
81
  const title = mode === "create" ? "샘플 등록" : "샘플 수정";
@@ -57,9 +93,11 @@ export function SampleFormModal({
57
93
  defaultValues={defaultValues ?? SAMPLE_FORM_DEFAULT_VALUES}
58
94
  onSubmit={onSubmit}
59
95
  >
96
+ <SampleNameChecker originalName={originalName} />
60
97
  <SubmitForm.Row formKey="name" label="이름" required maxLength={100} />
61
98
  <SubmitForm.Row formKey="description" formType="textarea" label="설명" required maxLength={500} />
62
99
  <SubmitForm.Row formKey="category" formType="radio" label="카테고리" options={CATEGORY_OPTIONS} />
100
+ <SubmitForm.Row formKey="priority" formType="select" label="우선순위" options={PRIORITY_OPTIONS} />
63
101
  <SubmitForm.Row formKey="active" formType="switch" label="사용 여부" />
64
102
  </SubmitForm>
65
103
  </div>
@@ -58,6 +58,7 @@ export default function SampleDetailPage() {
58
58
  name: data.name,
59
59
  description: data.description,
60
60
  category: data.category as "BASIC" | "ADVANCED",
61
+ priority: data.priority,
61
62
  active: data.active,
62
63
  },
63
64
  });
@@ -140,7 +141,7 @@ export default function SampleDetailPage() {
140
141
  };
141
142
 
142
143
  const editDefaultValues: SampleFormData | undefined = sample
143
- ? { name: sample.name, description: sample.description, category: sample.category, active: sample.active }
144
+ ? { name: sample.name, description: sample.description, category: sample.category, priority: sample.priority, active: sample.active }
144
145
  : undefined;
145
146
 
146
147
  return (
@@ -269,7 +270,6 @@ export default function SampleDetailPage() {
269
270
  <ul className="border-t border-gray-50 bg-gray-50/60">
270
271
  {depth2Replies.map((reply) => {
271
272
  const depth3Replies = reply.replies ?? [];
272
- const isReplyingToReply = replyingToId === reply.id;
273
273
  const isEditingReply = editingCommentId === reply.id;
274
274
 
275
275
  return (
@@ -297,21 +297,9 @@ export default function SampleDetailPage() {
297
297
  onCancel={() => setEditingCommentId(null)}
298
298
  />
299
299
  ) : (
300
- <>
301
- <p className="whitespace-pre-wrap text-sm text-gray-600">
302
- {reply.content}
303
- </p>
304
- {reply.content !== "삭제된 댓글입니다." && (
305
- <button
306
- type="button"
307
- onClick={() => setReplyingToId(isReplyingToReply ? null : reply.id)}
308
- className="mt-0.5 flex items-center gap-1 text-xs text-gray-400 transition-colors hover:text-blue-500"
309
- >
310
- <CornerDownRight size={11} />
311
- {isReplyingToReply ? "취소" : "답글"}
312
- </button>
313
- )}
314
- </>
300
+ <p className="whitespace-pre-wrap text-sm text-gray-600">
301
+ {reply.content}
302
+ </p>
315
303
  )}
316
304
  </div>
317
305
  {!isEditingReply && renderCommentActions(reply)}
@@ -364,18 +352,6 @@ export default function SampleDetailPage() {
364
352
  </ul>
365
353
  )}
366
354
 
367
- {/* depth 2 답글 입력창 */}
368
- {isReplyingToReply && !isEditingReply && (
369
- <div className="border-t border-blue-50 bg-blue-50/30 py-3 pl-10 pr-5">
370
- <CommentInput
371
- targetType="SAMPLE"
372
- targetId={sampleId}
373
- parentCommentId={reply.id}
374
- placeholder="답글을 입력하세요. @를 입력하면 사용자를 멘션할 수 있습니다."
375
- onSuccess={() => setReplyingToId(null)}
376
- />
377
- </div>
378
- )}
379
355
  </li>
380
356
  );
381
357
  })}
@@ -2,7 +2,7 @@ import { useState } from "react";
2
2
  import { useNavigate } from "react-router";
3
3
  import { Badge, Button, confirmModal, PageFilter, Select, Table } from "@farmzone/fz-react-ui";
4
4
 
5
- import { useDeleteSamples, useGetSamples, usePostSample, usePutSample } from "@/app/api/queries";
5
+ import { checkSampleNameAvailable, useDeleteSamples, useGetSamples, usePostSample, usePutSample } from "@/app/api/queries";
6
6
  import type { Sample } from "@/types";
7
7
  import ListContents from "@/app/layout/ListContents";
8
8
  import ListHeader from "@/app/layout/ListHeader";
@@ -75,19 +75,27 @@ export default function SamplePage() {
75
75
 
76
76
  const handleSubmit = async (data: SampleFormData) => {
77
77
  if (modalMode === "create") {
78
+ const available = await checkSampleNameAvailable(data.name);
79
+ if (!available) return;
78
80
  await postSample({
79
81
  name: data.name,
80
82
  description: data.description,
81
83
  category: data.category as "BASIC" | "ADVANCED",
84
+ priority: data.priority,
82
85
  active: data.active,
83
86
  });
84
87
  } else if (modalMode === "edit" && selectedSample) {
88
+ if (data.name !== selectedSample.name) {
89
+ const available = await checkSampleNameAvailable(data.name);
90
+ if (!available) return;
91
+ }
85
92
  await putSample({
86
93
  id: selectedSample.id,
87
94
  data: {
88
95
  name: data.name,
89
96
  description: data.description,
90
97
  category: data.category as "BASIC" | "ADVANCED",
98
+ priority: data.priority,
91
99
  active: data.active,
92
100
  },
93
101
  });
@@ -263,6 +271,7 @@ export default function SamplePage() {
263
271
  onSubmit={handleSubmit}
264
272
  isPending={isCreating || isUpdating}
265
273
  formKey={selectedSample?.id}
274
+ originalName={selectedSample?.name}
266
275
  />
267
276
  </div>
268
277
  );
@@ -1,6 +1,7 @@
1
1
  import { useState, type ReactNode } from "react";
2
2
  import { Button, Modal, Tab } from "@farmzone/fz-react-ui";
3
- import { ArrowRight, Clock, Maximize2, Minimize2, X } from "lucide-react";
3
+ import { ArrowRight, Clock, Download, Maximize2, Minimize2, X } from "lucide-react";
4
+ import { apiInstance } from "@/app/api/api";
4
5
  import ListHeader from "@/app/layout/ListHeader";
5
6
 
6
7
  const CATTLE = {
@@ -235,6 +236,27 @@ function CattleDetailModal({ isOpen, onClose }: { isOpen: boolean; onClose: () =
235
236
 
236
237
  export default function SampleModalPage() {
237
238
  const [isOpen, setIsOpen] = useState(false);
239
+ const [isDownloading, setIsDownloading] = useState(false);
240
+
241
+ const handleExcelTemplateDownload = async (menuCode: string) => {
242
+ setIsDownloading(true);
243
+ try {
244
+ const res = await apiInstance.get<Blob>(`/excel/template?menuCode=${menuCode}`, {
245
+ responseType: "blob",
246
+ });
247
+ const disposition = (res.headers as Record<string, string>)["content-disposition"] ?? "";
248
+ const fileNameMatch = /filename\*?=['"]?(?:UTF-8'')?([^;'"]+)['"]?/i.exec(disposition);
249
+ const fileName = fileNameMatch ? decodeURIComponent(fileNameMatch[1]) : "엑셀_업로드_양식.xlsx";
250
+ const url = URL.createObjectURL(res.data);
251
+ const anchor = document.createElement("a");
252
+ anchor.href = url;
253
+ anchor.download = fileName;
254
+ anchor.click();
255
+ URL.revokeObjectURL(url);
256
+ } finally {
257
+ setIsDownloading(false);
258
+ }
259
+ };
238
260
 
239
261
  return (
240
262
  <div className="p-6">
@@ -247,6 +269,31 @@ export default function SampleModalPage() {
247
269
  </Button>
248
270
  </div>
249
271
  </div>
272
+ <div className="mt-6 flex items-center justify-center rounded-xl border border-dashed border-gray-300 bg-white p-16">
273
+ <div className="text-center">
274
+ <p className="mb-4 text-sm text-gray-500">
275
+ 아래 버튼을 클릭하여 템플릿 다운로드 기능을 확인하세요.
276
+ </p>
277
+ <div className="flex flex-col gap-4">
278
+ <Button
279
+ variant="outline"
280
+ onClick={() => handleExcelTemplateDownload("SAMPLE")}
281
+ disabled={isDownloading}
282
+ >
283
+ <Download className="mr-1.5 h-4 w-4" />
284
+ 엑셀 업로드 양식 다운로드 (SAMPLE)
285
+ </Button>
286
+ {/* <Button
287
+ variant="outline"
288
+ onClick={() => handleExcelTemplateDownload("POST")}
289
+ disabled={isDownloading}
290
+ >
291
+ <Download className="mr-1.5 h-4 w-4" />
292
+ 엑셀 업로드 양식 다운로드 (POST)
293
+ </Button> */}
294
+ </div>
295
+ </div>
296
+ </div>
250
297
  <CattleDetailModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
251
298
  </div>
252
299
  );
@@ -1,5 +1,4 @@
1
- import { Badge, Button, type Column } from "@farmzone/fz-react-ui";
2
- import { Pencil, Trash2 } from "lucide-react";
1
+ import { Badge, type Column } from "@farmzone/fz-react-ui";
3
2
 
4
3
  import type { User } from "@/types";
5
4
  import { formatDateTime } from "@/shared/utils/format";
@@ -17,13 +16,7 @@ interface ActionHandlers {
17
16
  pageSize: number;
18
17
  }
19
18
 
20
- export const getUserColumns = ({
21
- onEdit,
22
- onDelete,
23
- totalElements,
24
- page,
25
- pageSize,
26
- }: ActionHandlers): Array<Column<User>> => [
19
+ export const getUserColumns = ({ totalElements, page, pageSize }: ActionHandlers): Array<Column<User>> => [
27
20
  {
28
21
  key: "index",
29
22
  title: "No",
@@ -140,25 +140,25 @@ const USER_READ_FIELDS: Array<DetailField<UserViewData>> = [
140
140
  {
141
141
  key: "role",
142
142
  label: "구분",
143
- render: ({ value }: { value: string }) => (
144
- <span className="text-sm text-gray-900">{value === "ADMIN" ? "관리자" : "일반"}</span>
143
+ render: ({ value }) => (
144
+ <span className="text-sm text-gray-900">{(value as string) === "ADMIN" ? "관리자" : "일반"}</span>
145
145
  ),
146
146
  },
147
147
  {
148
148
  key: "active",
149
149
  label: "사용여부",
150
- render: ({ value }: { value: boolean }) => (
150
+ render: ({ value }) => (
151
151
  <Badge
152
- text={value ? "활성" : "비활성"}
153
- className={`scale-90 ${value ? "bg-green-100 text-green-700 border-green-100" : "bg-gray-100 text-gray-500 border-gray-200"}`}
152
+ text={(value as boolean) ? "활성" : "비활성"}
153
+ className={`scale-90 ${(value as boolean) ? "bg-green-100 text-green-700 border-green-100" : "bg-gray-100 text-gray-500 border-gray-200"}`}
154
154
  />
155
155
  ),
156
156
  },
157
157
  {
158
158
  key: "gender",
159
159
  label: "성별",
160
- render: ({ value }: { value: string }) => (
161
- <span className="text-sm text-gray-900">{value === "M" ? "남" : value === "F" ? "여" : "-"}</span>
160
+ render: ({ value }) => (
161
+ <span className="text-sm text-gray-900">{(value as string) === "M" ? "남" : (value as string) === "F" ? "여" : "-"}</span>
162
162
  ),
163
163
  },
164
164
  { key: "birthday", label: "생년월일" },
@@ -24,5 +24,6 @@ export interface SampleForm {
24
24
  name: string;
25
25
  description: string;
26
26
  category: SampleCategory;
27
+ priority: number;
27
28
  active: boolean;
28
29
  }