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-button.tsx | 306 +++++++++++++-------------
 1 file changed, 153 insertions(+), 153 deletions(-)

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

diff --git a/components/information/information-button.tsx b/components/information/information-button.tsx
index da0de548..38e8cb12 100644
--- a/components/information/information-button.tsx
+++ b/components/information/information-button.tsx
@@ -1,137 +1,129 @@
 "use client"
 
-import React, { useState, useEffect } from "react"
-import { Info, Download, Edit } from "lucide-react"
+import * as React from "react"
+import { useState } from "react"
 import { Button } from "@/components/ui/button"
+
 import {
   Dialog,
   DialogContent,
-  DialogDescription,
   DialogHeader,
   DialogTitle,
   DialogTrigger,
 } from "@/components/ui/dialog"
+import { Info, Download, Edit } from "lucide-react"
 import { getCachedPageInformation, getCachedEditPermission } from "@/lib/information/service"
+import { getCachedPageNotices } from "@/lib/notice/service"
 import { UpdateInformationDialog } from "@/lib/information/table/update-information-dialog"
+import { NoticeViewDialog } from "@/components/notice/notice-view-dialog"
 import type { PageInformation } from "@/db/schema/information"
+import type { Notice } from "@/db/schema/notice"
 import { useSession } from "next-auth/react"
+import { formatDate } from "@/lib/utils"
 
 interface InformationButtonProps {
-  pageCode: string
+  pagePath: string
   className?: string
   variant?: "default" | "outline" | "ghost" | "secondary"
   size?: "default" | "sm" | "lg" | "icon"
 }
 
+type NoticeWithAuthor = Notice & {
+  authorName: string | null
+  authorEmail: string | null
+}
+
 export function InformationButton({ 
-  pageCode, 
+  pagePath, 
   className,
   variant = "ghost",
   size = "icon"
 }: InformationButtonProps) {
   const { data: session } = useSession()
-  const [information, setInformation] = useState<PageInformation | null>(null)
-  const [isLoading, setIsLoading] = useState(false)
   const [isOpen, setIsOpen] = useState(false)
+  const [information, setInformation] = useState<PageInformation | null>(null)
+  const [notices, setNotices] = useState<NoticeWithAuthor[]>([])
   const [hasEditPermission, setHasEditPermission] = useState(false)
   const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
+  const [selectedNotice, setSelectedNotice] = useState<NoticeWithAuthor | null>(null)
+  const [isNoticeViewDialogOpen, setIsNoticeViewDialogOpen] = useState(false)
+  const [dataLoaded, setDataLoaded] = useState(false)
 
-  useEffect(() => {
-    if (isOpen && !information) {
-      loadInformation()
-    }
-  }, [isOpen, information])
-
-  // 편집 권한 확인
-  useEffect(() => {
-    const checkEditPermission = async () => {
-      if (session?.user?.id) {
-        try {
-          const permission = await getCachedEditPermission(pageCode, session.user.id)
-          setHasEditPermission(permission)
-        } catch (error) {
-          console.error("Failed to check edit permission:", error)
-          setHasEditPermission(false)
-        }
-      }
-    }
+  // 데이터 로드 함수 (단순화)
+  const loadData = React.useCallback(async () => {
+    if (dataLoaded) return // 이미 로드되었으면 중복 방지
     
-    checkEditPermission()
-  }, [pageCode, session?.user?.id])
-
-  const loadInformation = async () => {
-    setIsLoading(true)
     try {
-      const data = await getCachedPageInformation(pageCode)
-      setInformation(data)
+      // pagePath 정규화 (앞의 / 제거)
+      const normalizedPath = pagePath.startsWith('/') ? pagePath.slice(1) : pagePath
+      
+      // 병렬로 데이터 조회
+      const [infoResult, noticesResult] = await Promise.all([
+        getCachedPageInformation(normalizedPath),
+        getCachedPageNotices(normalizedPath)
+      ])
+      
+      setInformation(infoResult)
+      setNotices(noticesResult)
+      setDataLoaded(true)
+      
+      // 권한 확인
+      if (session?.user?.id) {
+        const hasPermission = await getCachedEditPermission(normalizedPath, session.user.id)
+        setHasEditPermission(hasPermission)
+      }
     } catch (error) {
-      console.error("Failed to load information:", error)
-    } finally {
-      setIsLoading(false)
+      console.error("데이터 로딩 중 오류:", error)
     }
-  }
+  }, [pagePath, session?.user?.id, dataLoaded])
 
-  const handleDownload = () => {
-    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 handleDialogOpen = (open: boolean) => {
+    setIsOpen(open)
+    
+    if (open && !dataLoaded) {
+      loadData()
     }
   }
 
+  // 편집 관련 핸들러
   const handleEditClick = () => {
     setIsEditDialogOpen(true)
   }
 
-  const handleEditClose = () => {
+  const handleEditSuccess = () => {
     setIsEditDialogOpen(false)
-    refreshInformation()
+    // 편집 후 데이터 다시 로드
+    setDataLoaded(false)
+    loadData()
+  }
+
+  // 공지사항 클릭 핸들러
+  const handleNoticeClick = (notice: NoticeWithAuthor) => {
+    setSelectedNotice(notice)
+    setIsNoticeViewDialogOpen(true)
   }
 
-  const refreshInformation = () => {
-    // 편집 후 정보 다시 로드
-    setInformation(null)
-    if (isOpen) {
-      loadInformation()
+  // 파일 다운로드 핸들러
+  const handleDownload = () => {
+    if (information?.attachmentFilePath) {
+      window.open(information.attachmentFilePath, '_blank')
     }
-    // 캐시 무효화를 위해 다시 확인
-    setTimeout(() => {
-      loadInformation()
-    }, 500)
   }
 
-  // 인포메이션이 없으면 버튼을 숨김
-  const [hasInformation, setHasInformation] = useState<boolean | null>(null)
 
-  useEffect(() => {
-    const checkInformation = async () => {
-      try {
-        const data = await getCachedPageInformation(pageCode)
-        setHasInformation(!!data)
-      } catch {
-        setHasInformation(false)
-      }
-    }
-    checkInformation()
-  }, [pageCode])
 
-  // 인포메이션이 없으면 버튼을 숨김
-  if (hasInformation === false) {
-    return null
-  }
 
+  
   return (
     <>
-      <Dialog open={isOpen} onOpenChange={setIsOpen}>
+      <Dialog open={isOpen} onOpenChange={handleDialogOpen}>
         <DialogTrigger asChild>
           <Button 
             variant={variant} 
             size={size} 
             className={className}
-            title="페이지 정보"
+            title="인포메이션"
           >
             <Info className="h-4 w-4" />
             {size !== "icon" && <span className="ml-1">정보</span>}
@@ -141,13 +133,53 @@ export function InformationButton({
           <DialogHeader>
             <div className="flex items-center justify-between">
               <div className="flex items-center gap-2">
-                {/* <Info className="h-5 w-5" /> */}
                 <div>
-                  <DialogTitle>{information?.title || "페이지 정보"}</DialogTitle>
-                  <DialogDescription>{information?.pageName}</DialogDescription>
+                  <DialogTitle>{information?.pageName}</DialogTitle>
                 </div>
               </div>
-              {hasEditPermission && (
+            </div>
+          </DialogHeader>
+          
+          <div className="mt-4 space-y-6">
+            {/* 공지사항 섹션 */}
+            {notices.length > 0 && (
+              <div className="space-y-3">
+                <div className="flex items-center justify-between">
+                  <h4 className="font-semibold">공지사항</h4>
+                  <span className="text-xs text-gray-500">{notices.length}개</span>
+                </div>
+                <div className="max-h-60 overflow-y-auto border rounded-lg bg-gray-50 p-2">
+                  <div className="space-y-2">
+                    {notices.map((notice) => (
+                      <div
+                        key={notice.id}
+                        className="p-3 bg-white border rounded-lg hover:bg-gray-50 cursor-pointer transition-colors"
+                        onClick={() => handleNoticeClick(notice)}
+                      >
+                        <div className="space-y-1">
+                          <h5 className="font-medium text-sm line-clamp-2">
+                            {notice.title}
+                          </h5>
+                          <div className="flex items-center gap-3 text-xs text-gray-500">
+                            <span>{formatDate(notice.createdAt)}</span>
+                            {notice.authorName && (
+                              <span>{notice.authorName}</span>
+                            )}
+                          </div>
+                        </div>
+                      </div>
+                    ))}
+                  </div>
+                </div>
+              </div>
+            )}
+
+            {/* 인포메이션 컨텐츠 */}
+            {information?.informationContent && (
+              <div className="space-y-3">
+                <div className="flex items-center justify-between">
+                <h4 className="font-semibold">안내사항</h4>
+                {hasEditPermission && information && (
                 <Button
                   variant="outline"
                   size="sm"
@@ -158,100 +190,68 @@ export function InformationButton({
                   편집
                 </Button>
               )}
-            </div>
-          </DialogHeader>
-          
-          <div className="mt-4 space-y-6">
-            {isLoading ? (
-              <div className="flex items-center justify-center py-8">
-                <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
               </div>
-            ) : information ? (
-              <>
-                {/* 공지사항 */}
-                {(information.noticeTitle || information.noticeContent) && (
-                  <div className="space-y-4">
-                    <div className="flex items-center gap-2">
-                      <h4 className="font-semibold text-xl">공지사항</h4>
-                    </div>
-                    <div className="bg-blue-50 border-2 border-blue-200 rounded-xl p-6">
-                      {information.noticeTitle && (
-                        <div className="text-base font-semibold  mb-4">
-                          제목: {information.noticeTitle}
-                        </div>
-                      )}
-                      {information.noticeContent && (
-                        <div className="bg-white border-2 border-blue-200 rounded-lg p-4 max-h-48 overflow-y-auto">
-                          <div className="text-base  whitespace-pre-wrap leading-relaxed">
-                            {information.noticeContent}
-                          </div>
-                        </div>
-                      )}
-                    </div>
-                  </div>
-                )}
-
-                {/* 페이지 정보 */}
-                <div className="space-y-3">
-                  <h4 className="font-medium text-lg">도움말</h4>
-                  <div className="bg-gray-50 border rounded-lg p-4">
-                    <div className="text-sm text-gray-600 whitespace-pre-wrap max-h-40 overflow-y-auto">
-                      {information.description || "페이지 설명이 없습니다."}
-                    </div>
+                <div className="bg-gray-50 border rounded-lg p-4">
+                  <div className="text-sm text-gray-600 whitespace-pre-wrap max-h-40 overflow-y-auto">
+                    {information.informationContent}
                   </div>
                 </div>
+              </div>
+            )}
 
-                {/* 첨부파일 */}
-                <div className="space-y-3">
-                  <h4 className="font-medium text-lg">첨부파일</h4>
-                  {information.attachmentFileName ? (
-                    <div className="bg-gray-50 border rounded-lg p-4">
-                      <div className="flex items-center justify-between p-3 bg-white rounded border">
-                        <div className="flex-1">
-                          <div className="text-sm font-medium">
-                            {information.attachmentFileName}
-                          </div>
-                          {information.attachmentFileSize && (
-                            <div className="text-xs text-gray-500 mt-1">
-                              {information.attachmentFileSize}
-                            </div>
-                          )}
-                        </div>
-                        <Button
-                          size="sm"
-                          variant="outline"
-                          onClick={handleDownload}
-                          className="flex items-center gap-1"
-                        >
-                          <Download className="h-3 w-3" />
-                          다운로드
-                        </Button>
+            {/* 첨부파일 */}
+            {information?.attachmentFileName && (
+              <div className="space-y-3">
+                <h4 className="font-semibold">첨부파일</h4>
+                <div className="bg-gray-50 border rounded-lg p-4">
+                  <div className="flex items-center justify-between p-3 bg-white rounded border">
+                    <div className="flex-1">
+                      <div className="text-sm font-medium">
+                        {information.attachmentFileName}
                       </div>
+                      {information.attachmentFileSize && (
+                        <div className="text-xs text-gray-500 mt-1">
+                          {information.attachmentFileSize}
+                        </div>
+                      )}
                     </div>
-                  ) : (
-                    <div className="text-center py-6 text-gray-500 bg-gray-50 rounded-lg">
-                      <Download className="h-6 w-6 mx-auto mb-2 text-gray-400" />
-                      <p className="text-sm">첨부된 파일이 없습니다.</p>
-                    </div>
-                  )}
+                    <Button
+                      size="sm"
+                      variant="outline"
+                      onClick={handleDownload}
+                      className="flex items-center gap-1"
+                    >
+                      <Download className="h-3 w-3" />
+                      다운로드
+                    </Button>
+                  </div>
                 </div>
-              </>
-            ) : (
+              </div>
+            )}
+
+            {!information && notices.length === 0 && (
               <div className="text-center py-8 text-gray-500">
-                이 페이지에 대한 정보가 없습니다.
+                <p>이 페이지에 대한 정보가 없습니다.</p>
               </div>
             )}
           </div>
         </DialogContent>
       </Dialog>
 
+      {/* 공지사항 보기 다이얼로그 */}
+      <NoticeViewDialog
+        open={isNoticeViewDialogOpen}
+        onOpenChange={setIsNoticeViewDialogOpen}
+        notice={selectedNotice}
+      />
+
       {/* 편집 다이얼로그 */}
       {information && (
         <UpdateInformationDialog
           open={isEditDialogOpen}
           onOpenChange={setIsEditDialogOpen}
           information={information}
-          onClose={handleEditClose}
+          onSuccess={handleEditSuccess}
         />
       )}
     </>
-- 
cgit v1.2.3

