summaryrefslogtreecommitdiff
path: root/lib/risk-management/table/user-combo-box.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-08-14 00:26:53 +0000
committerjoonhoekim <26rote@gmail.com>2025-08-14 00:26:53 +0000
commitdd20ba9785cdbd3d61f6b014d003d3bd9646ad13 (patch)
tree4e99d62311a6c115dbc894084714a29c34bca11a /lib/risk-management/table/user-combo-box.tsx
parent33be47506f0aa62b969d82521580a29e95080268 (diff)
(고건) 리스크 관리 페이지 추가
Diffstat (limited to 'lib/risk-management/table/user-combo-box.tsx')
-rw-r--r--lib/risk-management/table/user-combo-box.tsx127
1 files changed, 127 insertions, 0 deletions
diff --git a/lib/risk-management/table/user-combo-box.tsx b/lib/risk-management/table/user-combo-box.tsx
new file mode 100644
index 00000000..e319b538
--- /dev/null
+++ b/lib/risk-management/table/user-combo-box.tsx
@@ -0,0 +1,127 @@
+'use client';
+
+/* IMPORT */
+import { Button } from '@/components/ui/button';
+import { Check, ChevronsUpDown } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from '@/components/ui/command';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { useMemo, useState } from 'react';
+import { User } from '@/db/schema';
+
+// ----------------------------------------------------------------------------------------------------
+
+/* TYPES */
+interface UserComboBoxProps {
+ users: Partial<User>[];
+ value: number | null;
+ onChange: (value: number) => void;
+ placeholder?: string;
+ disabled?: boolean;
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* User Combo Box Component */
+function UserComboBox(props: UserComboBoxProps) {
+ const {
+ users,
+ value,
+ onChange,
+ placeholder = '담당자 선택...',
+ disabled = false,
+ } = props;
+ const [open, setOpen] = useState(false);
+ const [inputValue, setInputValue] = useState('');
+ const selectedUser = useMemo(() => {
+ return users.find(user => user.id === value)
+ }, [users, value]);
+
+ return (
+ <Popover open={open} onOpenChange={setOpen}>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={open}
+ className={cn(
+ "w-full justify-between",
+ !value && "text-muted-foreground"
+ )}
+ disabled={disabled}
+ >
+ {selectedUser ? (
+ <span className="flex items-center">
+ <span className="font-medium">{selectedUser.name}</span>
+ {selectedUser.deptName && (
+ <span className="ml-2 text-xs text-muted-foreground">
+ ({selectedUser.deptName})
+ </span>
+ )}
+ </span>
+ ) : (
+ placeholder
+ )}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[300px] p-0">
+ <Command>
+ <CommandInput
+ placeholder="담당자 검색..."
+ value={inputValue}
+ onValueChange={setInputValue}
+ />
+ <CommandEmpty>검색 결과가 존재하지 않아요.</CommandEmpty>
+ <CommandGroup className="max-h-[200px] overflow-y-auto">
+ {users.map((user) => (
+ <CommandItem
+ key={user.id}
+ value={user.name}
+ onSelect={() => {
+ onChange(user.id!)
+ setOpen(false)
+ }}
+ >
+ <Check
+ className={cn(
+ 'mr-2 h-4 w-4',
+ value === user.id ? 'opacity-100' : 'opacity-0',
+ )}
+ />
+ <div className="flex flex-col truncate">
+ <div className="flex items-center">
+ <span className="font-medium">{user.name}</span>
+ {user.deptName && (
+ <span className="ml-2 text-xs text-muted-foreground">
+ ({user.deptName})
+ </span>
+ )}
+ </div>
+ <span className="text-xs text-muted-foreground truncate">
+ {user.email}
+ </span>
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ )
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* EXPORT */
+export default UserComboBox;