summaryrefslogtreecommitdiff
path: root/components/vendor-data
diff options
context:
space:
mode:
Diffstat (limited to 'components/vendor-data')
-rw-r--r--components/vendor-data/project-swicher.tsx177
1 files changed, 116 insertions, 61 deletions
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