summaryrefslogtreecommitdiff
path: root/components/vendor-data-plant/project-swicher.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-23 10:10:21 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-23 10:10:21 +0000
commitf7f5069a2209cfa39b65f492f32270a5f554bed0 (patch)
tree933c731ec2cb7d8bc62219a0aeed45a5e97d5f15 /components/vendor-data-plant/project-swicher.tsx
parentd49ad5dee1e5a504e1321f6db802b647497ee9ff (diff)
(대표님) EDP 해양 관련 개발 사항들
Diffstat (limited to 'components/vendor-data-plant/project-swicher.tsx')
-rw-r--r--components/vendor-data-plant/project-swicher.tsx171
1 files changed, 171 insertions, 0 deletions
diff --git a/components/vendor-data-plant/project-swicher.tsx b/components/vendor-data-plant/project-swicher.tsx
new file mode 100644
index 00000000..d3123709
--- /dev/null
+++ b/components/vendor-data-plant/project-swicher.tsx
@@ -0,0 +1,171 @@
+"use client"
+
+import * as React from "react"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import {
+ 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
+ contractName: string
+}
+
+interface ProjectInfo {
+ projectId: number
+ projectCode: string
+ projectName: string
+ contracts: ContractInfo[]
+}
+
+interface ProjectSwitcherProps {
+ isCollapsed: boolean
+ projects: ProjectInfo[]
+
+ // 상위가 관리하는 "현재 선택된 contractId"
+ selectedContractId: number | null
+
+ // 콜백: 사용자가 "어떤 contract"를 골랐는지
+ // => 우리가 projectId도 찾아서 상위 state를 같이 갱신해야 함
+ onSelectContract: (projectId: number, contractId: number) => void
+
+ // 로딩 상태 (선택사항)
+ isLoading?: boolean
+}
+
+export function ProjectSwitcher({
+ isCollapsed,
+ projects,
+ selectedContractId,
+ onSelectContract,
+ isLoading = false,
+}: ProjectSwitcherProps) {
+ const [popoverOpen, setPopoverOpen] = React.useState(false)
+ const [searchTerm, setSearchTerm] = React.useState("")
+
+ // 현재 선택된 contract 객체 찾기
+ const selectedContract = React.useMemo(() => {
+ if (!selectedContractId) return null
+ for (const proj of projects) {
+ const found = proj.contracts.find((c) => c.contractId === selectedContractId)
+ if (found) {
+ return { ...found, projectId: proj.projectId, projectName: proj.projectName }
+ }
+ }
+ return null
+ }, [projects, selectedContractId])
+
+ // Trigger label => 계약 이름 or placeholder
+ const triggerLabel = selectedContract?.contractName ?? "Select a contract"
+
+ // 검색어에 따른 필터링된 프로젝트/계약 목록
+ 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])
+
+ // 계약 선택 핸들러
+ function handleSelectContract(projectId: number, contractId: number) {
+ onSelectContract(projectId, contractId)
+ setPopoverOpen(false)
+ setSearchTerm("") // 검색어 초기화
+ }
+
+ // 총 계약 수 계산 (빈 상태 표시용)
+ const totalContracts = filteredProjects.reduce((sum, project) => sum + project.contracts.length, 0)
+
+ 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>
+ ))}
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ )
+} \ No newline at end of file