summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/information/information-button.tsx42
-rw-r--r--components/vendor-data/project-swicher.tsx177
2 files changed, 137 insertions, 82 deletions
diff --git a/components/information/information-button.tsx b/components/information/information-button.tsx
index 015894c1..1c6d4e7a 100644
--- a/components/information/information-button.tsx
+++ b/components/information/information-button.tsx
@@ -16,7 +16,7 @@ import { getPageInformationDirect, getEditPermissionDirect } from "@/lib/informa
import { getPageNotices } from "@/lib/notice/service"
import { UpdateInformationDialog } from "@/lib/information/table/update-information-dialog"
import { NoticeViewDialog } from "@/components/notice/notice-view-dialog"
-import { PDFTronViewerDialog } from "@/components/document-viewer/pdftron-viewer-dialog"
+// import { PDFTronViewerDialog } from "@/components/document-viewer/pdftron-viewer-dialog" // 주석 처리 - 브라우저 내장 뷰어 사용
import type { PageInformation, InformationAttachment } from "@/db/schema/information"
import type { Notice } from "@/db/schema/notice"
import { useSession } from "next-auth/react"
@@ -53,8 +53,8 @@ export function InformationButton({
const [dataLoaded, setDataLoaded] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [retryCount, setRetryCount] = useState(0)
- const [viewerDialogOpen, setViewerDialogOpen] = useState(false)
- const [selectedFile, setSelectedFile] = useState<InformationAttachment | null>(null)
+ // const [viewerDialogOpen, setViewerDialogOpen] = useState(false) // 주석 처리 - 브라우저 내장 뷰어 사용
+ // const [selectedFile, setSelectedFile] = useState<InformationAttachment | null>(null) // 주석 처리 - 브라우저 내장 뷰어 사용
// 데이터 로드 함수
const loadData = React.useCallback(async () => {
@@ -169,9 +169,10 @@ export function InformationButton({
// 파일 클릭 핸들러 (뷰어 또는 다운로드)
const handleFileClick = async (attachment: InformationAttachment) => {
if (isViewerSupported(attachment.fileName)) {
- // PDF/DOCX 파일은 뷰어로 열기
- setSelectedFile(attachment)
- setViewerDialogOpen(true)
+ // PDF/DOCX 파일은 브라우저 내장 뷰어로 열기
+ // 동적으로 quickPreview 함수 import
+ const { quickPreview } = await import('@/lib/file-download')
+ await quickPreview(attachment.filePath, attachment.fileName)
} else {
// 기타 파일은 다운로드
await handleDownload(attachment)
@@ -335,18 +336,17 @@ export function InformationButton({
)}
</div>
<div className="flex gap-2">
- {isViewerSupported(attachment.fileName) &&
- <Button
- size="sm"
- variant="outline"
- disabled={!isViewerSupported(attachment.fileName)}
- onClick={() => isViewerSupported(attachment.fileName) && handleFileClick(attachment)}
- className="flex items-center gap-1"
- >
- <EyeIcon className="h-3 w-3" />
- 미리보기
- </Button>
- }
+ {isViewerSupported(attachment.fileName) && (
+ <Button
+ size="sm"
+ variant="outline"
+ onClick={() => handleFileClick(attachment)}
+ className="flex items-center gap-1"
+ >
+ <EyeIcon className="h-3 w-3" />
+ 미리보기
+ </Button>
+ )}
<Button
size="sm"
variant="outline"
@@ -391,13 +391,13 @@ export function InformationButton({
/>
)}
- {/* PDFTron 뷰어 다이얼로그 */}
- <PDFTronViewerDialog
+ {/* PDFTron 뷰어 다이얼로그 - 주석 처리 (브라우저 내장 뷰어 사용) */}
+ {/* <PDFTronViewerDialog
open={viewerDialogOpen}
onOpenChange={setViewerDialogOpen}
fileUrl={selectedFile?.filePath}
fileName={selectedFile?.fileName}
- />
+ /> */}
</>
)
} \ No newline at end of file
diff --git a/components/vendor-data/project-swicher.tsx b/components/vendor-data/project-swicher.tsx
index 609de5cc..d3123709 100644
--- a/components/vendor-data/project-swicher.tsx
+++ b/components/vendor-data/project-swicher.tsx
@@ -2,15 +2,21 @@
import * as React from "react"
import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+import { Check, ChevronsUpDown, Loader2 } from "lucide-react"
interface ContractInfo {
contractId: number
@@ -27,13 +33,16 @@ interface ProjectInfo {
interface ProjectSwitcherProps {
isCollapsed: boolean
projects: ProjectInfo[]
-
+
// 상위가 관리하는 "현재 선택된 contractId"
selectedContractId: number | null
-
+
// 콜백: 사용자가 "어떤 contract"를 골랐는지
- // => 우리가 projectId도 찾아서 상위 state를 같이 갱신해야 함
+ // => 우리가 projectId도 찾아서 상위 state를 같이 갱신해야 함
onSelectContract: (projectId: number, contractId: number) => void
+
+ // 로딩 상태 (선택사항)
+ isLoading?: boolean
}
export function ProjectSwitcher({
@@ -41,9 +50,10 @@ export function ProjectSwitcher({
projects,
selectedContractId,
onSelectContract,
+ isLoading = false,
}: ProjectSwitcherProps) {
- // Select value = stringified contractId
- const selectValue = selectedContractId ? String(selectedContractId) : ""
+ const [popoverOpen, setPopoverOpen] = React.useState(false)
+ const [searchTerm, setSearchTerm] = React.useState("")
// 현재 선택된 contract 객체 찾기
const selectedContract = React.useMemo(() => {
@@ -51,7 +61,7 @@ export function ProjectSwitcher({
for (const proj of projects) {
const found = proj.contracts.find((c) => c.contractId === selectedContractId)
if (found) {
- return { ...found, projectId: proj.projectId }
+ return { ...found, projectId: proj.projectId, projectName: proj.projectName }
}
}
return null
@@ -60,57 +70,102 @@ export function ProjectSwitcher({
// Trigger label => 계약 이름 or placeholder
const triggerLabel = selectedContract?.contractName ?? "Select a contract"
- // onValueChange: val = String(contractId)
- // => 찾으면 projectId, contractId 모두 상위로 전달
- function handleValueChange(val: string) {
- const contractId = Number(val)
- // Find which project has this contract
- let foundProjectId = 0
- let foundContractName = ""
+ // 검색어에 따른 필터링된 프로젝트/계약 목록
+ const filteredProjects = React.useMemo(() => {
+ if (!searchTerm) return projects
+
+ return projects.map(project => ({
+ ...project,
+ contracts: project.contracts.filter(contract =>
+ contract.contractName.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ project.projectName.toLowerCase().includes(searchTerm.toLowerCase())
+ )
+ })).filter(project => project.contracts.length > 0)
+ }, [projects, searchTerm])
- for (const proj of projects) {
- const found = proj.contracts.find((c) => c.contractId === contractId)
- if (found) {
- foundProjectId = proj.projectId
- foundContractName = found.contractName
- break
- }
- }
- // 상위로 알림
- onSelectContract(foundProjectId, contractId)
+ // 계약 선택 핸들러
+ function handleSelectContract(projectId: number, contractId: number) {
+ onSelectContract(projectId, contractId)
+ setPopoverOpen(false)
+ setSearchTerm("") // 검색어 초기화
}
- return (
- <Select value={selectValue} onValueChange={handleValueChange}>
- <SelectTrigger
- className={cn(
- "flex items-center gap-2",
- isCollapsed && "flex h-9 w-9 shrink-0 items-center justify-center p-0"
- )}
- aria-label="Select Contract"
- >
- <SelectValue placeholder="Select a contract">
- <span className={cn("ml-2", isCollapsed && "hidden")}>
- {triggerLabel}
- </span>
- </SelectValue>
- </SelectTrigger>
+ // 총 계약 수 계산 (빈 상태 표시용)
+ const totalContracts = filteredProjects.reduce((sum, project) => sum + project.contracts.length, 0)
- <SelectContent>
- {projects.map((project) => (
- <SelectGroup key={project.projectCode}>
- <SelectLabel>{project.projectName}</SelectLabel>
- {project.contracts.map((contract) => (
- <SelectItem
- key={contract.contractId}
- value={String(contract.contractId)}
- >
- {contract.contractName}
- </SelectItem>
+ return (
+ <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
+ <PopoverTrigger asChild>
+ <Button
+ type="button"
+ variant="outline"
+ className={cn(
+ "justify-between relative",
+ isCollapsed ? "h-9 w-9 shrink-0 items-center justify-center p-0" : "w-full h-9"
+ )}
+ disabled={isLoading}
+ aria-label="Select Contract"
+ >
+ {isLoading ? (
+ <>
+ <span className={cn(isCollapsed && "hidden")}>Loading...</span>
+ <Loader2 className={cn("h-4 w-4 animate-spin", !isCollapsed && "ml-2")} />
+ </>
+ ) : (
+ <>
+ <span className={cn("truncate flex-grow text-left", isCollapsed && "hidden")}>
+ {triggerLabel}
+ </span>
+ <ChevronsUpDown className={cn("h-4 w-4 opacity-50 flex-shrink-0", isCollapsed && "hidden")} />
+ </>
+ )}
+ </Button>
+ </PopoverTrigger>
+
+ <PopoverContent className="w-[320px] p-0" align="start">
+ <Command>
+ <CommandInput
+ placeholder="Search contracts..."
+ value={searchTerm}
+ onValueChange={setSearchTerm}
+ />
+
+ <CommandList
+ className="max-h-[320px]"
+ onWheel={(e) => {
+ e.stopPropagation() // 이벤트 전파 차단
+ const target = e.currentTarget
+ target.scrollTop += e.deltaY // 직접 스크롤 처리
+ }}
+ >
+ <CommandEmpty>
+ {totalContracts === 0 ? "No contracts found." : "No search results."}
+ </CommandEmpty>
+
+ {filteredProjects.map((project) => (
+ <CommandGroup key={project.projectCode} heading={project.projectName}>
+ {project.contracts.map((contract) => (
+ <CommandItem
+ key={contract.contractId}
+ onSelect={() => handleSelectContract(project.projectId, contract.contractId)}
+ value={`${project.projectName} ${contract.contractName}`}
+ className="truncate"
+ title={contract.contractName}
+ >
+ <span className="truncate">{contract.contractName}</span>
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4 flex-shrink-0",
+ selectedContractId === contract.contractId ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </CommandItem>
+ ))}
+ </CommandGroup>
))}
- </SelectGroup>
- ))}
- </SelectContent>
- </Select>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
)
} \ No newline at end of file