diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-23 02:44:05 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-23 02:44:05 +0000 |
| commit | da00bbf203534b2663289d6fe45b6ed8663e7e11 (patch) | |
| tree | 859f0444b679807b1cc1b223aac4989958641d01 /components | |
| parent | c8e93fad9b1f3b1e4d99b23fc3d99dd5f463742a (diff) | |
(최겸) 구매 인포메이션, 공지사항 수정
Diffstat (limited to 'components')
| -rw-r--r-- | components/information/information-button.tsx | 29 | ||||
| -rw-r--r-- | components/information/information-client.tsx | 36 | ||||
| -rw-r--r-- | components/notice/notice-create-dialog.tsx | 107 |
3 files changed, 128 insertions, 44 deletions
diff --git a/components/information/information-button.tsx b/components/information/information-button.tsx index 2cff96d0..e9163093 100644 --- a/components/information/information-button.tsx +++ b/components/information/information-button.tsx @@ -18,6 +18,11 @@ import { UpdateInformationDialog } from "@/lib/information/table/update-informat import { NoticeViewDialog } from "@/components/notice/notice-view-dialog"
// import { PDFTronViewerDialog } from "@/components/document-viewer/pdftron-viewer-dialog" // 주석 처리 - 브라우저 내장 뷰어 사용
import type { PageInformation, InformationAttachment } from "@/db/schema/information"
+
+type PageInformationWithUpdatedBy = PageInformation & {
+ updatedByName?: string | null
+ updatedByEmail?: string | null
+}
import type { Notice } from "@/db/schema/notice"
import { useSession } from "next-auth/react"
import { formatDate } from "@/lib/utils"
@@ -44,7 +49,7 @@ export function InformationButton({ }: InformationButtonProps) {
const { data: session } = useSession()
const [isOpen, setIsOpen] = useState(false)
- const [information, setInformation] = useState<(PageInformation & { attachments: InformationAttachment[] }) | null>(null)
+ const [information, setInformation] = useState<PageInformationWithUpdatedBy & { attachments: InformationAttachment[] } | null>(null)
const [notices, setNotices] = useState<NoticeWithAuthor[]>([])
const [hasEditPermission, setHasEditPermission] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
@@ -211,10 +216,10 @@ export function InformationButton({ variant={variant}
size={size}
className={className}
- title="인포메이션"
+ title="안내사항"
>
<Info className="h-4 w-4" />
- {size !== "icon" && <span className="ml-1">정보</span>}
+ {size !== "icon" && <span className="ml-1">안내사항</span>}
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
@@ -277,7 +282,7 @@ export function InformationButton({ )}
</div>
- {/* 인포메이션 컨텐츠 */}
+ {/* 안내사항 컨텐츠 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h4 className="font-semibold">안내사항</h4>
@@ -295,8 +300,20 @@ export function InformationButton({ </div>
<div className="bg-muted/50 border rounded-lg p-4">
{information?.informationContent ? (
- <div className="text-sm text-muted-foreground whitespace-pre-wrap max-h-40 overflow-y-auto">
- {information.informationContent}
+ <div className="space-y-3">
+ <div className="text-sm text-muted-foreground whitespace-pre-wrap max-h-40 overflow-y-auto">
+ {information.informationContent}
+ </div>
+ <div className="flex items-center justify-between text-xs text-muted-foreground border-t pt-2">
+ <div className="flex items-center gap-4">
+ <span>
+ <strong>수정자:</strong> {information.updatedByName || '시스템'}
+ </span>
+ <span>
+ <strong>수정일:</strong> {formatDate(information.updatedAt, "KR")}
+ </span>
+ </div>
+ </div>
</div>
) : (
<div className="text-center text-muted-foreground">
diff --git a/components/information/information-client.tsx b/components/information/information-client.tsx index 48bb683d..53350d13 100644 --- a/components/information/information-client.tsx +++ b/components/information/information-client.tsx @@ -28,13 +28,18 @@ import { toast } from "sonner" import { formatDate } from "@/lib/utils"
import { getInformationLists, syncInformationFromMenuAssignments, getInformationDetail } from "@/lib/information/service"
import type { PageInformation } from "@/db/schema/information"
+
+type PageInformationWithUpdatedBy = PageInformation & {
+ updatedByName?: string | null
+ updatedByEmail?: string | null
+}
import { UpdateInformationDialog } from "@/lib/information/table/update-information-dialog"
interface InformationClientProps {
initialData?: PageInformation[]
}
-type SortField = "pageName" | "pagePath" | "createdAt"
+type SortField = "pageName" | "pagePath" | "updatedAt"
type SortDirection = "asc" | "desc"
export function InformationClient({ initialData = [] }: InformationClientProps) {
@@ -58,12 +63,12 @@ export function InformationClient({ initialData = [] }: InformationClientProps) }
}
- const [informations, setInformations] = useState<PageInformation[]>(initialData)
+ const [informations, setInformations] = useState<PageInformationWithUpdatedBy[]>(initialData)
const [loading, setLoading] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const [sortField, setSortField] = useState<SortField>("createdAt")
const [sortDirection, setSortDirection] = useState<SortDirection>("desc")
- const [editingInformation, setEditingInformation] = useState<PageInformation | null>(null)
+ const [editingInformation, setEditingInformation] = useState<PageInformationWithUpdatedBy | null>(null)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [isSyncing, setIsSyncing] = useState(false)
const [, startTransition] = useTransition()
@@ -327,13 +332,14 @@ export function InformationClient({ initialData = [] }: InformationClientProps) </TableHead>
<TableHead>정보 내용</TableHead>
<TableHead>상태</TableHead>
+ <TableHead>수정자</TableHead>
<TableHead>
<button
className="flex items-center gap-1 hover:text-foreground"
- onClick={() => handleSort("createdAt")}
+ onClick={() => handleSort("updatedAt")}
>
- 생성일
- {sortField === "createdAt" && (
+ 수정일
+ {sortField === "updatedAt" && (
sortDirection === "asc" ? (
<ChevronUp className="h-4 w-4" />
) : (
@@ -342,20 +348,21 @@ export function InformationClient({ initialData = [] }: InformationClientProps) )}
</button>
</TableHead>
+ <TableHead>수정일</TableHead>
<TableHead className="text-right">작업</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
- <TableCell colSpan={7} className="text-center py-8">
+ <TableCell colSpan={8} className="text-center py-8">
로딩 중...
</TableCell>
</TableRow>
) : filteredAndSortedInformations.length === 0 ? (
<TableRow>
- <TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
- 정보가 없습니다.
+ <TableCell colSpan={8} className="text-center py-8 text-muted-foreground">
+ 안내사항이 없습니다.
</TableCell>
</TableRow>
) : (
@@ -388,7 +395,16 @@ export function InformationClient({ initialData = [] }: InformationClientProps) </Badge>
</TableCell>
<TableCell>
- {formatDate(information.createdAt, "KR")}
+ {information.updatedByName || '시스템'}
+ </TableCell>
+ <TableCell>
+ {formatDate(information.updatedAt, "KR")}
+ </TableCell>
+ <TableCell>
+ {information.updatedAt && information.updatedAt !== information.createdAt
+ ? formatDate(information.updatedAt, "KR")
+ : '-'
+ }
</TableCell>
<TableCell className="text-right">
<Button
diff --git a/components/notice/notice-create-dialog.tsx b/components/notice/notice-create-dialog.tsx index 591b2bc7..98c66c99 100644 --- a/components/notice/notice-create-dialog.tsx +++ b/components/notice/notice-create-dialog.tsx @@ -7,7 +7,7 @@ import { useForm } from "react-hook-form" import { useParams } from "next/navigation"
import { useTranslation } from "@/i18n/client"
import { toast } from "sonner"
-import { Loader } from "lucide-react"
+import { Loader, Check, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
@@ -25,13 +25,9 @@ import { } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
+import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command"
+import { cn } from "@/lib/utils"
import TiptapEditor from "@/components/qna/tiptap-editor"
import { createNotice } from "@/lib/notice/service"
import { createNoticeSchema, type CreateNoticeSchema } from "@/lib/notice/validations"
@@ -135,26 +131,81 @@ export function NoticeCreateDialog({ <FormField
control={form.control}
name="pagePath"
- render={({ field }) => (
- <FormItem>
- <FormLabel>페이지 경로 *</FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="페이지를 선택하세요" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {pagePathOptions.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {safeTranslate(option.label)} - {option.value}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
+ render={({ field }) => {
+ const [open, setOpen] = useState(false)
+ const [searchTerm, setSearchTerm] = useState("")
+
+ const filteredOptions = React.useMemo(() => {
+ if (!searchTerm.trim()) return pagePathOptions
+
+ const lowerSearch = searchTerm.toLowerCase()
+ return pagePathOptions.filter(
+ (option) =>
+ safeTranslate(option.label).toLowerCase().includes(lowerSearch) ||
+ option.value.toLowerCase().includes(lowerSearch)
+ )
+ }, [pagePathOptions, searchTerm])
+
+ const selectedOption = pagePathOptions.find(option => option.value === field.value)
+
+ return (
+ <FormItem>
+ <FormLabel>페이지 경로 *</FormLabel>
+ <Popover open={open} onOpenChange={setOpen}>
+ <PopoverTrigger asChild>
+ <FormControl>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={open}
+ className="w-full justify-between"
+ >
+ {selectedOption
+ ? `${safeTranslate(selectedOption.label)} - ${selectedOption.value}`
+ : "페이지를 선택하세요"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </FormControl>
+ </PopoverTrigger>
+ <PopoverContent className="w-full p-0" align="start">
+ <Command>
+ <CommandInput
+ placeholder="페이지명 또는 경로 검색..."
+ onValueChange={setSearchTerm}
+ />
+ <CommandList className="max-h-[300px]">
+ <CommandEmpty>검색 결과가 없습니다</CommandEmpty>
+ <CommandGroup>
+ {filteredOptions.map((option) => (
+ <CommandItem
+ key={option.value}
+ value={`${safeTranslate(option.label)} ${option.value}`}
+ onSelect={() => {
+ field.onChange(option.value)
+ setOpen(false)
+ }}
+ >
+ <Check
+ className={cn(
+ "mr-2 h-4 w-4",
+ field.value === option.value
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ <span className="font-medium">{safeTranslate(option.label)}</span>
+ <span className="ml-2 text-gray-500 truncate">- {option.value}</span>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </FormItem>
+ )
+ }}
/>
<FormField
|
