From 795b4915069c44f500a91638e16ded67b9e16618 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Tue, 1 Jul 2025 11:46:33 +0000
Subject: (최겸) 정보시스템 공지사항 개발
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/information/information-client.tsx | 340 ++++++++++++++++++++++++++
 1 file changed, 340 insertions(+)
 create mode 100644 components/information/information-client.tsx

(limited to 'components/information/information-client.tsx')

diff --git a/components/information/information-client.tsx b/components/information/information-client.tsx
new file mode 100644
index 00000000..513b8f20
--- /dev/null
+++ b/components/information/information-client.tsx
@@ -0,0 +1,340 @@
+"use client"
+
+import { useState, useEffect, useTransition } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import {
+  Table,
+  TableBody,
+  TableCell,
+  TableHead,
+  TableHeader,
+  TableRow,
+} from "@/components/ui/table"
+import { Badge } from "@/components/ui/badge"
+import { 
+  Search, 
+  Edit, 
+  FileText, 
+  ChevronUp, 
+  ChevronDown, 
+  Download
+} from "lucide-react"
+import { toast } from "sonner"
+import { formatDate } from "@/lib/utils"
+import { getInformationLists } from "@/lib/information/service"
+import type { PageInformation } from "@/db/schema/information"
+import { UpdateInformationDialog } from "@/lib/information/table/update-information-dialog"
+
+interface InformationClientProps {
+  initialData?: PageInformation[]
+}
+
+type SortField = "pageName" | "pagePath" | "createdAt"
+type SortDirection = "asc" | "desc"
+
+export function InformationClient({ initialData = [] }: InformationClientProps) {
+  const [informations, setInformations] = useState<PageInformation[]>(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 [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
+  const [, startTransition] = useTransition()
+
+  // 정보 목록 조회
+  const fetchInformations = async () => {
+    try {
+      setLoading(true)
+      const search = searchQuery || undefined
+
+      startTransition(async () => {
+        const result = await getInformationLists({
+          page: 1,
+          perPage: 50,
+          search: search,
+          sort: [{ id: sortField, desc: sortDirection === "desc" }],
+          flags: [],
+          filters: [],
+          joinOperator: "and",
+          pagePath: "",
+          pageName: "",
+          informationContent: "",
+          isActive: null,
+        })
+
+        if (result?.data) {
+          setInformations(result.data)
+        } else {
+          toast.error("정보 목록을 가져오는데 실패했습니다.")
+        }
+        setLoading(false)
+      })
+    } catch (error) {
+      console.error("Error fetching informations:", error)
+      toast.error("정보 목록을 가져오는데 실패했습니다.")
+      setLoading(false)
+    }
+  }
+
+  // 검색 핸들러
+  const handleSearch = () => {
+    fetchInformations()
+  }
+
+  // 정렬 함수
+  const sortInformations = (informations: PageInformation[]) => {
+    return [...informations].sort((a, b) => {
+      let aValue: string | Date
+      let bValue: string | Date
+
+      if (sortField === "pageName") {
+        aValue = a.pageName
+        bValue = b.pageName
+      } else if (sortField === "pagePath") {
+        aValue = a.pagePath
+        bValue = b.pagePath
+      } else {
+        aValue = new Date(a.createdAt)
+        bValue = new Date(b.createdAt)
+      }
+
+      if (aValue < bValue) {
+        return sortDirection === "asc" ? -1 : 1
+      }
+      if (aValue > bValue) {
+        return sortDirection === "asc" ? 1 : -1
+      }
+      return 0
+    })
+  }
+
+  // 정렬 핸들러
+  const handleSort = (field: SortField) => {
+    if (sortField === field) {
+      setSortDirection(sortDirection === "asc" ? "desc" : "asc")
+    } else {
+      setSortField(field)
+      setSortDirection("asc")
+    }
+  }
+
+  // 편집 핸들러
+  const handleEdit = (information: PageInformation) => {
+    setEditingInformation(information)
+    setIsEditDialogOpen(true)
+  }
+
+  // 편집 완료 핸들러
+  const handleEditClose = () => {
+    setIsEditDialogOpen(false)
+    setEditingInformation(null)
+    // 데이터 새로고침
+    fetchInformations()
+  }
+
+  // 다운로드 핸들러
+  const handleDownload = (information: PageInformation) => {
+    if (information.attachmentFilePath && information.attachmentFileName) {
+      const link = document.createElement('a')
+      link.href = information.attachmentFilePath
+      link.download = information.attachmentFileName
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
+    }
+  }
+
+  // 정렬된 정보 목록
+  const sortedInformations = sortInformations(informations)
+
+  useEffect(() => {
+    if (initialData.length > 0) {
+      setInformations(initialData)
+    } else {
+      fetchInformations()
+    }
+  }, [])
+
+  useEffect(() => {
+    if (searchQuery !== "") {
+      fetchInformations()
+    } else if (initialData.length > 0) {
+      setInformations(initialData)
+    }
+  }, [searchQuery])
+
+  return (
+    <div className="space-y-6">
+      {/* 검색 */}
+      <div className="flex items-center justify-between gap-4">
+        <div className="flex items-center gap-4">
+          <div className="relative flex-1 max-w-md">
+            <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
+            <Input
+              placeholder="페이지명이나 경로로 검색..."
+              value={searchQuery}
+              onChange={(e) => setSearchQuery(e.target.value)}
+              className="pl-10"
+              onKeyPress={(e) => e.key === "Enter" && handleSearch()}
+            />
+          </div>
+          <Button onClick={handleSearch} variant="outline">
+            검색
+          </Button>
+          <Button
+            variant="outline"
+            onClick={() => window.location.reload()}
+          >
+            새로고침
+          </Button>
+        </div>
+      </div>
+
+      {/* 정보 테이블 */}
+      <div className="bg-white rounded-lg shadow">
+        <Table>
+          <TableHeader>
+            <TableRow>
+              <TableHead>
+                <button
+                  className="flex items-center gap-1 hover:text-foreground"
+                  onClick={() => handleSort("pageName")}
+                >
+                  페이지명
+                  {sortField === "pageName" && (
+                    sortDirection === "asc" ? (
+                      <ChevronUp className="h-4 w-4" />
+                    ) : (
+                      <ChevronDown className="h-4 w-4" />
+                    )
+                  )}
+                </button>
+              </TableHead>
+              <TableHead>
+                <button
+                  className="flex items-center gap-1 hover:text-foreground"
+                  onClick={() => handleSort("pagePath")}
+                >
+                  페이지 경로
+                  {sortField === "pagePath" && (
+                    sortDirection === "asc" ? (
+                      <ChevronUp className="h-4 w-4" />
+                    ) : (
+                      <ChevronDown className="h-4 w-4" />
+                    )
+                  )}
+                </button>
+              </TableHead>
+              <TableHead>정보 내용</TableHead>
+              <TableHead>첨부파일</TableHead>
+              <TableHead>상태</TableHead>
+              <TableHead>
+                <button
+                  className="flex items-center gap-1 hover:text-foreground"
+                  onClick={() => handleSort("createdAt")}
+                >
+                  생성일
+                  {sortField === "createdAt" && (
+                    sortDirection === "asc" ? (
+                      <ChevronUp className="h-4 w-4" />
+                    ) : (
+                      <ChevronDown className="h-4 w-4" />
+                    )
+                  )}
+                </button>
+              </TableHead>
+              <TableHead className="text-right">작업</TableHead>
+            </TableRow>
+          </TableHeader>
+          <TableBody>
+            {loading ? (
+              <TableRow>
+                <TableCell colSpan={7} className="text-center py-8">
+                  로딩 중...
+                </TableCell>
+              </TableRow>
+            ) : informations.length === 0 ? (
+              <TableRow>
+                <TableCell colSpan={7} className="text-center py-8 text-gray-500">
+                  정보가 없습니다.
+                </TableCell>
+              </TableRow>
+            ) : (
+              sortedInformations.map((information) => (
+                <TableRow key={information.id}>
+                  <TableCell className="font-medium">
+                    <div className="flex items-center gap-2">
+                      <FileText className="h-4 w-4" />
+                      <span className="max-w-[200px] truncate">
+                        {information.pageName}
+                      </span>
+                    </div>
+                  </TableCell>
+                  <TableCell>
+                    <span className="font-mono text-sm max-w-[200px] truncate block">
+                      {information.pagePath}
+                    </span>
+                  </TableCell>
+                  <TableCell>
+                    <div 
+                      className="max-w-[300px] text-sm text-gray-600 line-clamp-2"
+                      dangerouslySetInnerHTML={{ 
+                        __html: information.informationContent?.substring(0, 100) + '...' || '' 
+                      }}
+                    />
+                  </TableCell>
+                  <TableCell>
+                    {information.attachmentFileName ? (
+                      <Button
+                        variant="outline"
+                        size="sm"
+                        onClick={() => handleDownload(information)}
+                        className="flex items-center gap-1"
+                      >
+                        <Download className="h-3 w-3" />
+                        <span className="max-w-[100px] truncate">
+                          {information.attachmentFileName}
+                        </span>
+                      </Button>
+                    ) : (
+                      <span className="text-gray-400">없음</span>
+                    )}
+                  </TableCell>
+                  <TableCell>
+                    <Badge variant={information.isActive ? "default" : "secondary"}>
+                      {information.isActive ? "활성" : "비활성"}
+                    </Badge>
+                  </TableCell>
+                  <TableCell>
+                    {formatDate(information.createdAt)}
+                  </TableCell>
+                  <TableCell className="text-right">
+                    <Button 
+                      variant="outline" 
+                      size="sm"
+                      onClick={() => handleEdit(information)}
+                    >
+                      <Edit className="h-4 w-4" />
+                    </Button>
+                  </TableCell>
+                </TableRow>
+              ))
+            )}
+          </TableBody>
+        </Table>
+      </div>
+
+      {/* 편집 다이얼로그 */}
+      {editingInformation && (
+        <UpdateInformationDialog
+          open={isEditDialogOpen}
+          onOpenChange={setIsEditDialogOpen}
+          information={editingInformation}
+          onSuccess={handleEditClose}
+        />
+      )}
+    </div>
+  )
+} 
\ No newline at end of file
-- 
cgit v1.2.3

