diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/information/repository.ts | 58 | ||||
| -rw-r--r-- | lib/information/service.ts | 224 | ||||
| -rw-r--r-- | lib/information/table/add-information-dialog.tsx | 329 | ||||
| -rw-r--r-- | lib/information/table/delete-information-dialog.tsx | 125 | ||||
| -rw-r--r-- | lib/information/table/information-table-columns.tsx | 248 | ||||
| -rw-r--r-- | lib/information/table/information-table-toolbar-actions.tsx | 25 | ||||
| -rw-r--r-- | lib/information/table/information-table.tsx | 148 | ||||
| -rw-r--r-- | lib/information/table/update-information-dialog.tsx | 124 | ||||
| -rw-r--r-- | lib/information/validations.ts | 32 |
9 files changed, 124 insertions, 1189 deletions
diff --git a/lib/information/repository.ts b/lib/information/repository.ts index 2a3bc1c0..f640a4c6 100644 --- a/lib/information/repository.ts +++ b/lib/information/repository.ts @@ -40,19 +40,15 @@ export async function countInformationLists( // 기존 패턴 (하위 호환성을 위해 유지)
export async function selectInformation(input: GetInformationSchema) {
- const { page, per_page = 50, sort, pageCode, pageName, isActive, from, to } = input
+ const { page, per_page = 50, sort, pagePath, isActive, from, to } = input
const conditions = []
- if (pageCode) {
- conditions.push(ilike(pageInformation.pageCode, `%${pageCode}%`))
+ if (pagePath) {
+ conditions.push(ilike(pageInformation.pagePath, `%${pagePath}%`))
}
- if (pageName) {
- conditions.push(ilike(pageInformation.pageName, `%${pageName}%`))
- }
-
- if (isActive !== null) {
+ if (isActive !== null && isActive !== undefined) {
conditions.push(eq(pageInformation.isActive, isActive))
}
@@ -91,19 +87,15 @@ export async function selectInformation(input: GetInformationSchema) { // 기존 패턴: 인포메이션 총 개수 조회
export async function countInformation(input: GetInformationSchema) {
- const { pageCode, pageName, isActive, from, to } = input
+ const { pagePath, isActive, from, to } = input
const conditions = []
- if (pageCode) {
- conditions.push(ilike(pageInformation.pageCode, `%${pageCode}%`))
+ if (pagePath) {
+ conditions.push(ilike(pageInformation.pagePath, `%${pagePath}%`))
}
- if (pageName) {
- conditions.push(ilike(pageInformation.pageName, `%${pageName}%`))
- }
-
- if (isActive !== null) {
+ if (isActive !== null && isActive !== undefined) {
conditions.push(eq(pageInformation.isActive, isActive))
}
@@ -125,13 +117,13 @@ export async function countInformation(input: GetInformationSchema) { return result[0]?.count ?? 0
}
-// 페이지 코드별 인포메이션 조회 (활성화된 것만)
-export async function getInformationByPageCode(pageCode: string): Promise<PageInformation | null> {
+// 페이지 경로별 인포메이션 조회 (활성화된 것만)
+export async function getInformationByPagePath(pagePath: string): Promise<PageInformation | null> {
const result = await db
.select()
.from(pageInformation)
.where(and(
- eq(pageInformation.pageCode, pageCode),
+ eq(pageInformation.pagePath, pagePath),
eq(pageInformation.isActive, true)
))
.limit(1)
@@ -139,16 +131,6 @@ export async function getInformationByPageCode(pageCode: string): Promise<PageIn return result[0] || null
}
-// 인포메이션 생성
-export async function insertInformation(data: NewPageInformation): Promise<PageInformation> {
- const result = await db
- .insert(pageInformation)
- .values(data)
- .returning()
-
- return result[0]
-}
-
// 인포메이션 수정
export async function updateInformation(id: number, data: Partial<NewPageInformation>): Promise<PageInformation | null> {
const result = await db
@@ -160,24 +142,6 @@ export async function updateInformation(id: number, data: Partial<NewPageInforma return result[0] || null
}
-// 인포메이션 삭제
-export async function deleteInformationById(id: number): Promise<boolean> {
- const result = await db
- .delete(pageInformation)
- .where(eq(pageInformation.id, id))
-
- return (result.rowCount ?? 0) > 0
-}
-
-// 인포메이션 다중 삭제
-export async function deleteInformationByIds(ids: number[]): Promise<number> {
- const result = await db
- .delete(pageInformation)
- .where(sql`${pageInformation.id} = ANY(${ids})`)
-
- return result.rowCount ?? 0
-}
-
// ID로 인포메이션 조회
export async function getInformationById(id: number): Promise<PageInformation | null> {
const result = await db
diff --git a/lib/information/service.ts b/lib/information/service.ts index 8f1e5679..30a651f1 100644 --- a/lib/information/service.ts +++ b/lib/information/service.ts @@ -9,7 +9,6 @@ import db from "@/db/db" import { pageInformation, menuAssignments } from "@/db/schema"
import type {
- CreateInformationSchema,
UpdateInformationSchema,
GetInformationSchema
} from "./validations"
@@ -17,11 +16,8 @@ import type { import {
selectInformation,
countInformation,
- getInformationByPageCode,
- insertInformation,
+ getInformationByPagePath,
updateInformation,
- deleteInformationById,
- deleteInformationByIds,
getInformationById,
selectInformationLists,
countInformationLists
@@ -34,57 +30,65 @@ export async function getInformationLists(input: GetInformationSchema) { return unstable_cache(
async () => {
try {
- const offset = (input.page - 1) * input.perPage
+ // 고급 검색 로직
+ const { page, perPage, search, filters, joinOperator, pagePath, pageName, informationContent, isActive } = input
- // 고급 필터링
- const advancedWhere = filterColumns({
- table: pageInformation,
- filters: input.filters,
- joinOperator: input.joinOperator,
- })
+ // 기본 검색 조건들
+ const conditions = []
- // 전역 검색
- let globalWhere
- if (input.search) {
- const s = `%${input.search}%`
- globalWhere = or(
- ilike(pageInformation.pageCode, s),
- ilike(pageInformation.pageName, s),
- ilike(pageInformation.title, s),
- ilike(pageInformation.description, s)
- )
+ // 검색어가 있으면 여러 필드에서 검색
+ if (search && search.trim()) {
+ const searchConditions = [
+ ilike(pageInformation.pagePath, `%${search}%`),
+ ilike(pageInformation.pageName, `%${search}%`),
+ ilike(pageInformation.informationContent, `%${search}%`)
+ ]
+ conditions.push(or(...searchConditions))
}
- // 기본 필터들
- let basicWhere
- const basicConditions = []
-
- if (input.pageCode) {
- basicConditions.push(ilike(pageInformation.pageCode, `%${input.pageCode}%`))
+ // 개별 필드 조건들
+ if (pagePath && pagePath.trim()) {
+ conditions.push(ilike(pageInformation.pagePath, `%${pagePath}%`))
}
-
- if (input.pageName) {
- basicConditions.push(ilike(pageInformation.pageName, `%${input.pageName}%`))
+
+ if (pageName && pageName.trim()) {
+ conditions.push(ilike(pageInformation.pageName, `%${pageName}%`))
}
-
- if (input.title) {
- basicConditions.push(ilike(pageInformation.title, `%${input.title}%`))
+
+ if (informationContent && informationContent.trim()) {
+ conditions.push(ilike(pageInformation.informationContent, `%${informationContent}%`))
}
-
- if (input.isActive !== undefined && input.isActive !== null) {
- basicConditions.push(eq(pageInformation.isActive, input.isActive))
- }
-
- if (basicConditions.length > 0) {
- basicWhere = and(...basicConditions)
- }
-
- // 최종 where 조건
- const finalWhere = and(
- advancedWhere,
- globalWhere,
- basicWhere
- )
+
+ if (isActive !== null && isActive !== undefined) {
+ conditions.push(eq(pageInformation.isActive, isActive))
+ }
+
+ // 고급 필터 처리
+ if (filters && filters.length > 0) {
+ const advancedConditions = filters.map(() =>
+ filterColumns({
+ table: pageInformation,
+ filters: filters,
+ joinOperator: joinOperator,
+ })
+ )
+
+ if (advancedConditions.length > 0) {
+ if (joinOperator === "or") {
+ conditions.push(or(...advancedConditions))
+ } else {
+ conditions.push(and(...advancedConditions))
+ }
+ }
+ }
+
+ // 전체 WHERE 조건 조합
+ const finalWhere = conditions.length > 0
+ ? (joinOperator === "or" ? or(...conditions) : and(...conditions))
+ : undefined
+
+ // 페이지네이션
+ const offset = (page - 1) * perPage
// 정렬 처리
const orderBy = input.sort.length > 0
@@ -93,12 +97,12 @@ export async function getInformationLists(input: GetInformationSchema) { return item.desc ? desc(pageInformation.createdAt) : asc(pageInformation.createdAt)
} else if (item.id === "updatedAt") {
return item.desc ? desc(pageInformation.updatedAt) : asc(pageInformation.updatedAt)
- } else if (item.id === "pageCode") {
- return item.desc ? desc(pageInformation.pageCode) : asc(pageInformation.pageCode)
+ } else if (item.id === "pagePath") {
+ return item.desc ? desc(pageInformation.pagePath) : asc(pageInformation.pagePath)
} else if (item.id === "pageName") {
return item.desc ? desc(pageInformation.pageName) : asc(pageInformation.pageName)
- } else if (item.id === "title") {
- return item.desc ? desc(pageInformation.title) : asc(pageInformation.title)
+ } else if (item.id === "informationContent") {
+ return item.desc ? desc(pageInformation.informationContent) : asc(pageInformation.informationContent)
} else if (item.id === "isActive") {
return item.desc ? desc(pageInformation.isActive) : asc(pageInformation.isActive)
} else {
@@ -129,7 +133,7 @@ export async function getInformationLists(input: GetInformationSchema) { return { data: [], pageCount: 0, total: 0 }
}
},
- [JSON.stringify(input)], // 캐싱 키
+ [JSON.stringify(input)],
{
revalidate: 3600,
tags: ["information-lists"],
@@ -161,18 +165,18 @@ export async function getInformationList(input: Partial<GetInformationSchema> & }
// 페이지별 인포메이션 조회 (일반 사용자용)
-export async function getPageInformation(pageCode: string): Promise<PageInformation | null> {
+export async function getPageInformation(pagePath: string): Promise<PageInformation | null> {
try {
- return await getInformationByPageCode(pageCode)
+ return await getInformationByPagePath(pagePath)
} catch (error) {
- console.error(`Failed to get information for page ${pageCode}:`, error)
+ console.error(`Failed to get information for page ${pagePath}:`, error)
return null
}
}
// 캐시된 페이지별 인포메이션 조회
export const getCachedPageInformation = unstable_cache(
- async (pageCode: string) => getPageInformation(pageCode),
+ async (pagePath: string) => getPageInformation(pagePath),
["page-information"],
{
tags: ["page-information"],
@@ -180,34 +184,20 @@ export const getCachedPageInformation = unstable_cache( }
)
-// 인포메이션 생성
-export async function createInformation(input: CreateInformationSchema) {
- try {
- const result = await insertInformation(input)
-
- revalidateTag("page-information")
- revalidateTag("information-lists")
- revalidateTag("information-edit-permission")
-
- return {
- success: true,
- data: result,
- message: "인포메이션이 성공적으로 생성되었습니다."
- }
- } catch (error) {
- console.error("Failed to create information:", error)
- return {
- success: false,
- message: getErrorMessage(error)
- }
- }
-}
-
-// 인포메이션 수정
+// 인포메이션 수정 (내용과 첨부파일만)
export async function updateInformationData(input: UpdateInformationSchema) {
try {
const { id, ...updateData } = input
- const result = await updateInformation(id, updateData)
+
+ // 수정 가능한 필드만 허용
+ const allowedFields = {
+ informationContent: updateData.informationContent,
+ attachmentFilePath: updateData.attachmentFilePath,
+ attachmentFileName: updateData.attachmentFileName,
+ updatedAt: new Date()
+ }
+
+ const result = await updateInformation(id, allowedFields)
if (!result) {
return {
@@ -233,56 +223,6 @@ export async function updateInformationData(input: UpdateInformationSchema) { }
}
-// 인포메이션 삭제
-export async function deleteInformation(id: number) {
- try {
- const success = await deleteInformationById(id)
-
- if (!success) {
- return {
- success: false,
- message: "인포메이션을 찾을 수 없거나 삭제에 실패했습니다."
- }
- }
-
- revalidateTag("page-information")
- revalidateTag("information-lists")
-
- return {
- success: true,
- message: "인포메이션이 성공적으로 삭제되었습니다."
- }
- } catch (error) {
- console.error("Failed to delete information:", error)
- return {
- success: false,
- message: getErrorMessage(error)
- }
- }
-}
-
-// 인포메이션 다중 삭제
-export async function deleteMultipleInformation(ids: number[]) {
- try {
- const deletedCount = await deleteInformationByIds(ids)
-
- revalidateTag("page-information")
- revalidateTag("information-lists")
-
- return {
- success: true,
- deletedCount,
- message: `${deletedCount}개의 인포메이션이 성공적으로 삭제되었습니다.`
- }
- } catch (error) {
- console.error("Failed to delete multiple information:", error)
- return {
- success: false,
- message: getErrorMessage(error)
- }
- }
-}
-
// ID로 인포메이션 조회
export async function getInformationDetail(id: number): Promise<PageInformation | null> {
try {
@@ -294,18 +234,18 @@ export async function getInformationDetail(id: number): Promise<PageInformation }
// 인포메이션 편집 권한 확인
-export async function checkInformationEditPermission(pageCode: string, userId: string): Promise<boolean> {
+export async function checkInformationEditPermission(pagePath: string, userId: string): Promise<boolean> {
try {
- // pageCode를 menuPath로 변환 (pageCode가 menuPath의 마지막 부분이라고 가정)
- // 예: pageCode "vendor-list" -> menuPath "/evcp/vendor-list" 또는 "/partners/vendor-list"
+ // pagePath를 menuPath로 변환 (pagePath가 menuPath의 마지막 부분이라고 가정)
+ // 예: pagePath "vendor-list" -> menuPath "/evcp/vendor-list" 또는 "/partners/vendor-list"
const menuPathQueries = [
- `/evcp/${pageCode}`,
- `/partners/${pageCode}`,
- `/${pageCode}`, // 루트 경로
- pageCode // 정확한 매칭
+ `/evcp/${pagePath}`,
+ `/partners/${pagePath}`,
+ `/${pagePath}`, // 루트 경로
+ pagePath // 정확한 매칭
]
- // menu_assignments에서 해당 pageCode와 매칭되는 메뉴 찾기
+ // menu_assignments에서 해당 pagePath와 매칭되는 메뉴 찾기
const menuAssignment = await db
.select()
.from(menuAssignments)
@@ -334,7 +274,7 @@ export async function checkInformationEditPermission(pageCode: string, userId: s // 캐시된 권한 확인
export const getCachedEditPermission = unstable_cache(
- async (pageCode: string, userId: string) => checkInformationEditPermission(pageCode, userId),
+ async (pagePath: string, userId: string) => checkInformationEditPermission(pagePath, userId),
["information-edit-permission"],
{
tags: ["information-edit-permission"],
diff --git a/lib/information/table/add-information-dialog.tsx b/lib/information/table/add-information-dialog.tsx deleted file mode 100644 index a879fbfe..00000000 --- a/lib/information/table/add-information-dialog.tsx +++ /dev/null @@ -1,329 +0,0 @@ -"use client"
-
-import * as React from "react"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useForm } from "react-hook-form"
-import { toast } from "sonner"
-import { Loader, Upload, X } from "lucide-react"
-import { useRouter } from "next/navigation"
-
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog"
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form"
-import { Input } from "@/components/ui/input"
-import { Textarea } from "@/components/ui/textarea"
-import { Switch } from "@/components/ui/switch"
-import { createInformation } from "@/lib/information/service"
-import { createInformationSchema, type CreateInformationSchema } from "@/lib/information/validations"
-
-interface AddInformationDialogProps {
- open: boolean
- onOpenChange: (open: boolean) => void
-}
-
-export function AddInformationDialog({
- open,
- onOpenChange,
-}: AddInformationDialogProps) {
- const router = useRouter()
- const [isLoading, setIsLoading] = React.useState(false)
- const [uploadedFile, setUploadedFile] = React.useState<File | null>(null)
-
- const form = useForm<CreateInformationSchema>({
- resolver: zodResolver(createInformationSchema),
- defaultValues: {
- pageCode: "",
- pageName: "",
- title: "",
- description: "",
- noticeTitle: "",
- noticeContent: "",
- attachmentFileName: "",
- attachmentFilePath: "",
- attachmentFileSize: "",
- isActive: true,
- },
- })
-
- const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
- const file = event.target.files?.[0]
- if (file) {
- setUploadedFile(file)
- // 파일 크기를 MB 단위로 변환
- const sizeInMB = (file.size / (1024 * 1024)).toFixed(2)
- form.setValue("attachmentFileName", file.name)
- form.setValue("attachmentFileSize", `${sizeInMB} MB`)
- }
- }
-
- const removeFile = () => {
- setUploadedFile(null)
- form.setValue("attachmentFileName", "")
- form.setValue("attachmentFilePath", "")
- form.setValue("attachmentFileSize", "")
- }
-
- const uploadFile = async (file: File): Promise<string> => {
- const formData = new FormData()
- formData.append("file", file)
-
- const response = await fetch("/api/upload", {
- method: "POST",
- body: formData,
- })
-
- if (!response.ok) {
- throw new Error("파일 업로드에 실패했습니다.")
- }
-
- const result = await response.json()
- return result.url
- }
-
- const onSubmit = async (values: CreateInformationSchema) => {
- setIsLoading(true)
- try {
- const finalValues = { ...values }
-
- // 파일이 있으면 업로드
- if (uploadedFile) {
- const filePath = await uploadFile(uploadedFile)
- finalValues.attachmentFilePath = filePath
- }
-
- const result = await createInformation(finalValues)
-
- if (result.success) {
- toast.success(result.message)
- form.reset()
- setUploadedFile(null)
- onOpenChange(false)
- router.refresh()
- } else {
- toast.error(result.message)
- }
- } catch (error) {
- toast.error("인포메이션 생성에 실패했습니다.")
- console.error(error)
- } finally {
- setIsLoading(false)
- }
- }
-
- // 다이얼로그가 닫힐 때 폼 초기화
- React.useEffect(() => {
- if (!open) {
- form.reset()
- setUploadedFile(null)
- }
- }, [open, form])
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="max-w-2xl">
- <DialogHeader>
- <DialogTitle>인포메이션 추가</DialogTitle>
- <DialogDescription>
- 새로운 페이지 인포메이션을 추가합니다.
- </DialogDescription>
- </DialogHeader>
-
- <Form {...form}>
- <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
- <div className="grid grid-cols-2 gap-4">
- <FormField
- control={form.control}
- name="pageCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>페이지 코드</FormLabel>
- <FormControl>
- <Input placeholder="예: vendor-list" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="pageName"
- render={({ field }) => (
- <FormItem>
- <FormLabel>페이지명</FormLabel>
- <FormControl>
- <Input placeholder="예: 협력업체 목록" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- </div>
-
- <FormField
- control={form.control}
- name="title"
- render={({ field }) => (
- <FormItem>
- <FormLabel>제목</FormLabel>
- <FormControl>
- <Input placeholder="인포메이션 제목을 입력하세요" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="description"
- render={({ field }) => (
- <FormItem>
- <FormLabel>설명</FormLabel>
- <FormControl>
- <Textarea
- placeholder="페이지 설명을 입력하세요"
- rows={4}
- {...field}
- />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="noticeTitle"
- render={({ field }) => (
- <FormItem>
- <FormLabel>공지사항 제목 (선택사항)</FormLabel>
- <FormControl>
- <Input placeholder="공지사항 제목을 입력하세요" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="noticeContent"
- render={({ field }) => (
- <FormItem>
- <FormLabel>공지사항 내용 (선택사항)</FormLabel>
- <FormControl>
- <Textarea
- placeholder="공지사항 내용을 입력하세요"
- rows={3}
- {...field}
- />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <div>
- <FormLabel>첨부파일</FormLabel>
- <div className="mt-2">
- {uploadedFile ? (
- <div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
- <div className="flex items-center gap-2">
- <span className="text-sm font-medium">{uploadedFile.name}</span>
- <span className="text-xs text-gray-500">
- ({(uploadedFile.size / (1024 * 1024)).toFixed(2)} MB)
- </span>
- </div>
- <Button
- type="button"
- variant="ghost"
- size="sm"
- onClick={removeFile}
- >
- <X className="h-4 w-4" />
- </Button>
- </div>
- ) : (
- <div className="border-2 border-dashed border-gray-300 rounded-lg p-4">
- <div className="text-center">
- <Upload className="mx-auto h-8 w-8 text-gray-400" />
- <div className="mt-2">
- <label
- htmlFor="file-upload"
- className="cursor-pointer text-sm text-blue-600 hover:text-blue-500"
- >
- 파일을 선택하세요
- </label>
- <input
- id="file-upload"
- type="file"
- className="hidden"
- onChange={handleFileSelect}
- accept=".pdf,.doc,.docx,.xlsx,.ppt,.pptx,.txt,.zip"
- />
- </div>
- <p className="text-xs text-gray-500 mt-1">
- PDF, DOC, DOCX, XLSX, PPT, PPTX, TXT, ZIP 파일만 업로드 가능
- </p>
- </div>
- </div>
- )}
- </div>
- </div>
-
- <FormField
- control={form.control}
- name="isActive"
- render={({ field }) => (
- <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
- <div className="space-y-0.5">
- <FormLabel className="text-base">활성 상태</FormLabel>
- <div className="text-sm text-muted-foreground">
- 활성화하면 해당 페이지에서 인포메이션 버튼이 표시됩니다.
- </div>
- </div>
- <FormControl>
- <Switch
- checked={field.value}
- onCheckedChange={field.onChange}
- />
- </FormControl>
- </FormItem>
- )}
- />
-
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- onClick={() => onOpenChange(false)}
- disabled={isLoading}
- >
- 취소
- </Button>
- <Button type="submit" disabled={isLoading}>
- {isLoading && <Loader className="mr-2 h-4 w-4 animate-spin" />}
- 생성
- </Button>
- </DialogFooter>
- </form>
- </Form>
- </DialogContent>
- </Dialog>
- )
-}
\ No newline at end of file diff --git a/lib/information/table/delete-information-dialog.tsx b/lib/information/table/delete-information-dialog.tsx deleted file mode 100644 index e36d948d..00000000 --- a/lib/information/table/delete-information-dialog.tsx +++ /dev/null @@ -1,125 +0,0 @@ -"use client"
-
-import * as React from "react"
-import { useRouter } from "next/navigation"
-import { toast } from "sonner"
-import { Loader, Trash2 } from "lucide-react"
-
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@/components/ui/alert-dialog"
-import { deleteInformation } from "@/lib/information/service"
-import type { PageInformation } from "@/db/schema/information"
-
-interface DeleteInformationDialogProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- information?: PageInformation
- onClose: () => void
-}
-
-export function DeleteInformationDialog({
- open,
- onOpenChange,
- information,
- onClose,
-}: DeleteInformationDialogProps) {
- const router = useRouter()
- const [isLoading, setIsLoading] = React.useState(false)
-
- const handleDelete = async () => {
- if (!information) return
-
- setIsLoading(true)
- try {
- const result = await deleteInformation(information.id)
-
- if (result.success) {
- toast.success(result.message)
- onClose()
- router.refresh()
- } else {
- toast.error(result.message)
- }
- } catch (error) {
- toast.error("인포메이션 삭제에 실패했습니다.")
- console.error(error)
- } finally {
- setIsLoading(false)
- }
- }
-
- return (
- <AlertDialog open={open} onOpenChange={onOpenChange}>
- <AlertDialogContent>
- <AlertDialogHeader>
- <AlertDialogTitle className="flex items-center gap-2">
- <Trash2 className="h-5 w-5 text-destructive" />
- 인포메이션 삭제
- </AlertDialogTitle>
- <AlertDialogDescription>
- 다음 인포메이션을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
- </AlertDialogDescription>
- </AlertDialogHeader>
-
- {information && (
- <div className="bg-muted rounded-lg p-4 my-4">
- <div className="space-y-2">
- <div>
- <span className="font-medium text-sm">페이지 코드:</span>
- <span className="ml-2 font-mono text-sm">{information.pageCode}</span>
- </div>
- <div>
- <span className="font-medium text-sm">페이지명:</span>
- <span className="ml-2 text-sm">{information.pageName}</span>
- </div>
- <div>
- <span className="font-medium text-sm">제목:</span>
- <span className="ml-2 text-sm">{information.title}</span>
- </div>
- {information.noticeTitle && (
- <div>
- <span className="font-medium text-sm">공지사항 제목:</span>
- <span className="ml-2 text-sm">{information.noticeTitle}</span>
- </div>
- )}
- {information.noticeContent && (
- <div>
- <span className="font-medium text-sm">공지사항 내용:</span>
- <span className="ml-2 text-sm text-orange-600">{information.noticeContent}</span>
- </div>
- )}
- {information.attachmentFileName && (
- <div>
- <span className="font-medium text-sm">첨부파일:</span>
- <span className="ml-2 text-sm">{information.attachmentFileName}</span>
- </div>
- )}
- </div>
- </div>
- )}
-
- <AlertDialogFooter>
- <AlertDialogCancel onClick={onClose} disabled={isLoading}>
- 취소
- </AlertDialogCancel>
- <AlertDialogAction
- onClick={handleDelete}
- disabled={isLoading}
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
- >
- {isLoading && <Loader className="mr-2 h-4 w-4 animate-spin" />}
- 삭제
- </AlertDialogAction>
- </AlertDialogFooter>
- </AlertDialogContent>
- </AlertDialog>
- )
-}
\ No newline at end of file diff --git a/lib/information/table/information-table-columns.tsx b/lib/information/table/information-table-columns.tsx deleted file mode 100644 index f84fd2f9..00000000 --- a/lib/information/table/information-table-columns.tsx +++ /dev/null @@ -1,248 +0,0 @@ -"use client" - -import * as React from "react" -import type { ColumnDef } from "@tanstack/react-table" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" -import type { DataTableRowAction } from "@/types/table" -import type { PageInformation } from "@/db/schema/information" -import { formatDate } from "@/lib/utils" -import { Ellipsis, FileText, Download } from "lucide-react" -import { informationColumnsConfig } from "@/config/informationColumnsConfig" - -interface GetColumnsProps { - setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<PageInformation> | null>> -} - -/** - * tanstack table 컬럼 정의 (중첩 헤더 버전) - */ -export function getInformationColumns({ setRowAction }: GetColumnsProps): ColumnDef<PageInformation>[] { - // // ---------------------------------------------------------------- - // // 1) Select 컬럼 (체크박스) - // // ---------------------------------------------------------------- - // const selectColumn: ColumnDef<PageInformation> = { - // id: "select", - // header: ({ table }) => ( - // <Checkbox - // checked={ - // table.getIsAllPageRowsSelected() || - // (table.getIsSomePageRowsSelected() && "indeterminate") - // } - // onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} - // aria-label="Select all" - // className="translate-y-0.5" - // /> - // ), - // cell: ({ row }) => ( - // <Checkbox - // checked={row.getIsSelected()} - // onCheckedChange={(value) => row.toggleSelected(!!value)} - // aria-label="Select row" - // className="translate-y-0.5" - // /> - // ), - // enableSorting: false, - // enableHiding: false, - // } - - // ---------------------------------------------------------------- - // 2) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성 - // ---------------------------------------------------------------- - const groupMap: Record<string, ColumnDef<PageInformation>[]> = {} - - informationColumnsConfig.forEach((cfg) => { - // 만약 group가 없으면 "_noGroup" 처리 - const groupName = cfg.group || "_noGroup" - - if (!groupMap[groupName]) { - groupMap[groupName] = [] - } - - // child column 정의 - const childCol: ColumnDef<PageInformation> = { - accessorKey: cfg.id, - enableResizing: cfg.id === "description" || cfg.id === "noticeContent" ? false : true, - size: cfg.id === "description" || cfg.id === "noticeContent" ? 200 : undefined, - minSize: cfg.id === "description" || cfg.id === "noticeContent" ? 200 : undefined, - maxSize: cfg.id === "description" || cfg.id === "noticeContent" ? 200 : undefined, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={cfg.label} /> - ), - meta: { - excelHeader: cfg.excelHeader, - group: cfg.group, - type: cfg.type, - }, - cell: ({ row }) => { - const value = row.getValue(cfg.id) - - if (cfg.id === "pageCode") { - return <div className=" text-sm">{value as string}</div> - } - - if (cfg.id === "pageName") { - return <div className="max-w-4 truncate font-medium">{value as string}</div> - } - - if (cfg.id === "title") { - return <div className="max-w-4 truncate">{value as string}</div> - } - - if (cfg.id === "description") { - return ( - <div className="truncate text-muted-foreground" style={{ width: '200px', maxWidth: '200px' }}> - {value as string} - </div> - ) - } - - if (cfg.id === "noticeTitle") { - const noticeTitle = value as string - if (!noticeTitle) { - return <span className="text-muted-foreground">-</span> - } - return <div className="max-w-xs truncate">{noticeTitle}</div> - } - - if (cfg.id === "noticeContent") { - const noticeContent = value as string - if (!noticeContent) { - return <span className="text-muted-foreground">-</span> - } - return ( - <div className="truncate text-muted-foreground" style={{ width: '200px', maxWidth: '200px' }}> - {noticeContent} - </div> - ) - } - - if (cfg.id === "attachmentFileName") { - const fileName = value as string - if (!fileName) { - return <span className="text-muted-foreground">-</span> - } - return ( - <div className="flex items-center gap-1"> - <FileText className="h-3 w-3" /> - <span className="text-sm truncate max-w-32" title={fileName}> - {fileName} - </span> - </div> - ) - } - - if (cfg.id === "isActive") { - return ( - <Badge variant={value ? "default" : "secondary"}> - {value ? "활성" : "비활성"} - </Badge> - ) - } - - if (cfg.id === "createdAt" || cfg.id === "updatedAt") { - const dateVal = value as Date - return formatDate(dateVal) - } - - return value ?? "" - }, - } - - groupMap[groupName].push(childCol) - }) - - // ---------------------------------------------------------------- - // 3) groupMap에서 실제 상위 컬럼(그룹)을 만들기 - // ---------------------------------------------------------------- - const nestedColumns: ColumnDef<PageInformation>[] = [] - - // 순서를 고정하고 싶다면 group 순서를 미리 정의하거나 sort해야 함 - // 여기서는 그냥 Object.entries 순서 - Object.entries(groupMap).forEach(([groupName, colDefs]) => { - if (groupName === "_noGroup") { - // 그룹 없음 → 그냥 최상위 레벨 컬럼 - nestedColumns.push(...colDefs) - } else { - // 상위 컬럼 - nestedColumns.push({ - id: groupName, - header: groupName, // "기본 정보", "공지사항" 등 - columns: colDefs, - }) - } - }) - - // ---------------------------------------------------------------- - // 4) Actions 컬럼 - // ---------------------------------------------------------------- - const actionsColumn: ColumnDef<PageInformation> = { - id: "actions", - cell: ({ row }) => ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button - aria-label="Open menu" - variant="ghost" - className="flex size-8 p-0 data-[state=open]:bg-muted" - > - <Ellipsis className="size-4" aria-hidden="true" /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end" className="w-40"> - <DropdownMenuItem - onSelect={() => setRowAction({ row, type: "update" })} - > - 수정 - </DropdownMenuItem> - {row.original.attachmentFileName && ( - <> - <DropdownMenuSeparator /> - <DropdownMenuItem - onSelect={() => { - if (row.original.attachmentFilePath) { - const link = document.createElement('a') - link.href = row.original.attachmentFilePath - link.download = row.original.attachmentFileName || '' - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - } - }} - > - <Download className="mr-2 h-4 w-4" /> - 다운로드 - </DropdownMenuItem> - </> - )} - <DropdownMenuSeparator /> - <DropdownMenuItem - onSelect={() => setRowAction({ row, type: "delete" })} - className="text-destructive" - > - 삭제 - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ), - enableSorting: false, - enableHiding: false, - } - - // ---------------------------------------------------------------- - // 5) 최종 컬럼 배열: select, nestedColumns, actions - // ---------------------------------------------------------------- - return [ - // selectColumn, - ...nestedColumns, - actionsColumn, - ] -}
\ No newline at end of file diff --git a/lib/information/table/information-table-toolbar-actions.tsx b/lib/information/table/information-table-toolbar-actions.tsx deleted file mode 100644 index 5d8fff3a..00000000 --- a/lib/information/table/information-table-toolbar-actions.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"
-
-import { type Table } from "@tanstack/react-table"
-import { Plus } from "lucide-react"
-
-import { Button } from "@/components/ui/button"
-import type { PageInformation } from "@/db/schema/information"
-
-interface InformationTableToolbarActionsProps {
- table: Table<PageInformation>
- onAdd: () => void
-}
-
-export function InformationTableToolbarActions({
- onAdd,
-}: InformationTableToolbarActionsProps) {
- return (
- <div className="flex items-center gap-2">
- <Button size="sm" onClick={onAdd}>
- <Plus className="mr-2 size-4" aria-hidden="true" />
- 인포메이션 추가
- </Button>
- </div>
- )
-}
\ No newline at end of file diff --git a/lib/information/table/information-table.tsx b/lib/information/table/information-table.tsx deleted file mode 100644 index 9fc4ec29..00000000 --- a/lib/information/table/information-table.tsx +++ /dev/null @@ -1,148 +0,0 @@ -"use client"
-
-import * as React from "react"
-import type {
- DataTableAdvancedFilterField,
- DataTableFilterField,
- DataTableRowAction,
-} from "@/types/table"
-
-import { useDataTable } from "@/hooks/use-data-table"
-import { DataTable } from "@/components/data-table/data-table"
-import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
-import type { PageInformation } from "@/db/schema/information"
-import { getInformationColumns } from "./information-table-columns"
-import { InformationTableToolbarActions } from "./information-table-toolbar-actions"
-import { AddInformationDialog } from "./add-information-dialog"
-import { UpdateInformationDialog } from "./update-information-dialog"
-import { DeleteInformationDialog } from "./delete-information-dialog"
-
-interface InformationTableProps {
- promises: Promise<{
- data: PageInformation[]
- pageCount: number
- total: number
- }>
-}
-
-export function InformationTable({ promises }: InformationTableProps) {
- const [rowAction, setRowAction] = React.useState<DataTableRowAction<PageInformation> | null>(null)
- const [showAddDialog, setShowAddDialog] = React.useState(false)
- const [showUpdateDialog, setShowUpdateDialog] = React.useState(false)
- const [showDeleteDialog, setShowDeleteDialog] = React.useState(false)
-
- const { data, pageCount } = React.use(promises)
-
- // 컬럼 설정
- const columns = React.useMemo(
- () => getInformationColumns({ setRowAction }),
- [setRowAction]
- )
-
- const filterFields: DataTableFilterField<PageInformation>[] = []
-
- // 고급 필터 필드 설정
- const advancedFilterFields: DataTableAdvancedFilterField<PageInformation>[] = [
- {
- id: "pageCode",
- label: "페이지 코드",
- type: "text",
- },
- {
- id: "pageName",
- label: "페이지명",
- type: "text",
- },
- {
- id: "title",
- label: "제목",
- type: "text",
- },
- {
- id: "isActive",
- label: "상태",
- type: "select",
- options: [
- { label: "활성", value: "true" },
- { label: "비활성", value: "false" },
- ],
- },
- {
- id: "createdAt",
- label: "생성일",
- type: "date",
- },
- {
- id: "updatedAt",
- label: "수정일",
- type: "date",
- },
- ]
-
- const { table } = useDataTable({
- data,
- columns,
- pageCount,
- filterFields,
- enablePinning: true,
- enableAdvancedFilter: true,
- initialState: {
- sorting: [{ id: "createdAt", desc: true }],
- columnPinning: { right: ["actions"] },
- },
- getRowId: (originalRow) => String(originalRow.id),
- shallow: false,
- clearOnDefault: true,
- })
-
- // 행 액션 처리
- React.useEffect(() => {
- if (rowAction?.type === "update") {
- setShowUpdateDialog(true)
- } else if (rowAction?.type === "delete") {
- setShowDeleteDialog(true)
- }
- }, [rowAction])
-
- return (
- <>
- <DataTable table={table}>
- <DataTableAdvancedToolbar
- table={table}
- filterFields={advancedFilterFields}
- shallow={false}
- >
- <InformationTableToolbarActions
- table={table}
- onAdd={() => setShowAddDialog(true)}
- />
- </DataTableAdvancedToolbar>
- </DataTable>
-
- <AddInformationDialog
- open={showAddDialog}
- onOpenChange={setShowAddDialog}
- />
-
- <UpdateInformationDialog
- open={showUpdateDialog}
- onOpenChange={setShowUpdateDialog}
- information={rowAction?.row.original}
- onClose={() => {
- setShowUpdateDialog(false)
- setRowAction(null)
- }}
- />
-
- <DeleteInformationDialog
- open={showDeleteDialog}
- onOpenChange={setShowDeleteDialog}
- information={rowAction?.row.original}
- onClose={() => {
- setShowDeleteDialog(false)
- setRowAction(null)
- }}
- />
- </>
- )
-}
\ No newline at end of file diff --git a/lib/information/table/update-information-dialog.tsx b/lib/information/table/update-information-dialog.tsx index afa7559b..ed749fe7 100644 --- a/lib/information/table/update-information-dialog.tsx +++ b/lib/information/table/update-information-dialog.tsx @@ -24,7 +24,7 @@ import { FormLabel,
FormMessage,
} from "@/components/ui/form"
-import { Input } from "@/components/ui/input"
+
import { Textarea } from "@/components/ui/textarea"
import { Switch } from "@/components/ui/switch"
import { updateInformationData } from "@/lib/information/service"
@@ -35,14 +35,14 @@ interface UpdateInformationDialogProps { open: boolean
onOpenChange: (open: boolean) => void
information?: PageInformation
- onClose: () => void
+ onSuccess?: () => void
}
export function UpdateInformationDialog({
open,
onOpenChange,
information,
- onClose,
+ onSuccess,
}: UpdateInformationDialogProps) {
const router = useRouter()
const [isLoading, setIsLoading] = React.useState(false)
@@ -52,12 +52,10 @@ export function UpdateInformationDialog({ resolver: zodResolver(updateInformationSchema),
defaultValues: {
id: 0,
- pageCode: "",
- pageName: "",
- title: "",
- description: "",
- noticeTitle: "",
- noticeContent: "",
+ informationContent: "",
+ attachmentFileName: "",
+ attachmentFilePath: "",
+ attachmentFileSize: "",
isActive: true,
},
})
@@ -67,12 +65,7 @@ export function UpdateInformationDialog({ if (information && open) {
form.reset({
id: information.id,
- pageCode: information.pageCode,
- pageName: information.pageName,
- title: information.title,
- description: information.description,
- noticeTitle: information.noticeTitle || "",
- noticeContent: information.noticeContent || "",
+ informationContent: information.informationContent || "",
attachmentFileName: information.attachmentFileName || "",
attachmentFilePath: information.attachmentFilePath || "",
attachmentFileSize: information.attachmentFileSize || "",
@@ -131,7 +124,8 @@ export function UpdateInformationDialog({ if (result.success) {
toast.success(result.message)
- onClose()
+ if (onSuccess) onSuccess()
+ onOpenChange(false)
router.refresh()
} else {
toast.error(result.message)
@@ -146,7 +140,7 @@ export function UpdateInformationDialog({ const handleClose = () => {
setUploadedFile(null)
- onClose()
+ onOpenChange(false)
}
const currentFileName = form.watch("attachmentFileName")
@@ -163,92 +157,26 @@ export function UpdateInformationDialog({ <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
- <div className="grid grid-cols-2 gap-4">
- <FormField
- control={form.control}
- name="pageCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>페이지 코드</FormLabel>
- <FormControl>
- <Input placeholder="예: vendor-list" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="pageName"
- render={({ field }) => (
- <FormItem>
- <FormLabel>페이지명</FormLabel>
- <FormControl>
- <Input placeholder="예: 협력업체 목록" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ <div className="bg-blue-50 p-4 rounded-lg">
+ <div className="flex items-center gap-2 mb-2">
+ <span className="font-medium text-blue-900">페이지 정보</span>
+ </div>
+ <div className="text-sm text-blue-700">
+ <div><strong>페이지명:</strong> {information?.pageName}</div>
+ <div><strong>경로:</strong> {information?.pagePath}</div>
+ </div>
</div>
<FormField
control={form.control}
- name="title"
- render={({ field }) => (
- <FormItem>
- <FormLabel>제목</FormLabel>
- <FormControl>
- <Input placeholder="인포메이션 제목을 입력하세요" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="description"
- render={({ field }) => (
- <FormItem>
- <FormLabel>설명</FormLabel>
- <FormControl>
- <Textarea
- placeholder="페이지 설명을 입력하세요"
- rows={4}
- {...field}
- />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="noticeTitle"
- render={({ field }) => (
- <FormItem>
- <FormLabel>공지사항 제목 (선택사항)</FormLabel>
- <FormControl>
- <Input placeholder="공지사항 제목을 입력하세요" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="noticeContent"
+ name="informationContent"
render={({ field }) => (
<FormItem>
- <FormLabel>공지사항 내용 (선택사항)</FormLabel>
+ <FormLabel>인포메이션 내용</FormLabel>
<FormControl>
<Textarea
- placeholder="공지사항 내용을 입력하세요"
- rows={3}
+ placeholder="인포메이션 내용을 입력하세요"
+ rows={6}
{...field}
/>
</FormControl>
@@ -267,7 +195,7 @@ export function UpdateInformationDialog({ <span className="text-xs text-gray-500">
({(uploadedFile.size / (1024 * 1024)).toFixed(2)} MB)
</span>
- <span className="text-xs text-blue-600">(새 파일)</span>
+ <span className="text-xs">(새 파일)</span>
</div>
<Button
type="button"
@@ -291,7 +219,7 @@ export function UpdateInformationDialog({ <div className="flex gap-2">
<label
htmlFor="file-upload-update"
- className="cursor-pointer text-sm text-blue-600 hover:text-blue-500"
+ className="cursor-pointer text-sm"
>
변경
</label>
@@ -312,7 +240,7 @@ export function UpdateInformationDialog({ <div className="mt-2">
<label
htmlFor="file-upload-update"
- className="cursor-pointer text-sm text-blue-600 hover:text-blue-500"
+ className="cursor-pointer text-sm"
>
파일을 선택하세요
</label>
diff --git a/lib/information/validations.ts b/lib/information/validations.ts index 216e3354..c4f5d530 100644 --- a/lib/information/validations.ts +++ b/lib/information/validations.ts @@ -10,29 +10,10 @@ import { import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
import { PageInformation } from "@/db/schema/information"
-// 인포메이션 생성 스키마
-export const createInformationSchema = z.object({
- pageCode: z.string().min(1, "페이지 코드를 입력해주세요"),
- pageName: z.string().min(1, "페이지명을 입력해주세요"),
- title: z.string().min(1, "제목을 입력해주세요"),
- description: z.string().min(1, "설명을 입력해주세요"),
- noticeTitle: z.string().optional(),
- noticeContent: z.string().optional(),
- attachmentFileName: z.string().optional(),
- attachmentFilePath: z.string().optional(),
- attachmentFileSize: z.string().optional(),
- isActive: z.boolean().default(true),
-})
-
// 인포메이션 수정 스키마
export const updateInformationSchema = z.object({
id: z.number(),
- pageCode: z.string().min(1, "페이지 코드를 입력해주세요"),
- pageName: z.string().min(1, "페이지명을 입력해주세요"),
- title: z.string().min(1, "제목을 입력해주세요"),
- description: z.string().min(1, "설명을 입력해주세요"),
- noticeTitle: z.string().optional(),
- noticeContent: z.string().optional(),
+ informationContent: z.string().min(1, "내용을 입력해주세요"),
attachmentFileName: z.string().optional(),
attachmentFilePath: z.string().optional(),
attachmentFileSize: z.string().optional(),
@@ -49,9 +30,9 @@ export const searchParamsInformationCache = createSearchParamsCache({ ]),
// 기본 검색 필드들
- pageCode: parseAsString.withDefault(""),
+ pagePath: parseAsString.withDefault(""),
pageName: parseAsString.withDefault(""),
- title: parseAsString.withDefault(""),
+ informationContent: parseAsString.withDefault(""),
isActive: parseAsBoolean,
// 고급 필터
@@ -65,7 +46,6 @@ export const searchParamsInformationCache = createSearchParamsCache({ })
// 타입 추출
-export type CreateInformationSchema = z.infer<typeof createInformationSchema>
export type UpdateInformationSchema = z.infer<typeof updateInformationSchema>
export type GetInformationSchema = Awaited<ReturnType<typeof searchParamsInformationCache.parse>>
@@ -74,16 +54,14 @@ export const getInformationSchema = z.object({ page: z.coerce.number().default(1),
per_page: z.coerce.number().default(10),
sort: z.string().optional(),
- pageCode: z.string().optional(),
- pageName: z.string().optional(),
isActive: z.coerce.boolean().optional(),
from: z.string().optional(),
to: z.string().optional(),
})
-// 페이지 코드별 인포메이션 조회 스키마
+// 페이지 경로별 인포메이션 조회 스키마
export const getPageInformationSchema = z.object({
- pageCode: z.string().min(1, "페이지 코드를 입력해주세요"),
+ pagePath: z.string().min(1, "페이지 경로를 입력해주세요"),
})
export type GetPageInformationSchema = z.infer<typeof getPageInformationSchema>
\ No newline at end of file |
