엔티티타입 입력 셀렉트박스 다중선택 기능

This commit is contained in:
kjs
2026-01-08 14:49:24 +09:00
parent 3f81c449ad
commit 11e25694b9
11 changed files with 547 additions and 99 deletions

View File

@@ -11,7 +11,9 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Search, Loader2 } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { Search, Loader2, Check } from "lucide-react";
import { cn } from "@/lib/utils";
import { useEntitySearch } from "./useEntitySearch";
import { EntitySearchResult } from "./types";
@@ -26,6 +28,9 @@ interface EntitySearchModalProps {
modalTitle?: string;
modalColumns?: string[];
onSelect: (value: any, fullData: EntitySearchResult) => void;
// 다중선택 관련
multiple?: boolean;
selectedValues?: string[]; // 이미 선택된 값들
}
export function EntitySearchModal({
@@ -39,6 +44,8 @@ export function EntitySearchModal({
modalTitle = "검색",
modalColumns = [],
onSelect,
multiple = false,
selectedValues = [],
}: EntitySearchModalProps) {
const [localSearchText, setLocalSearchText] = useState("");
const {
@@ -71,7 +78,15 @@ export function EntitySearchModal({
const handleSelect = (item: EntitySearchResult) => {
onSelect(item[valueField], item);
onOpenChange(false);
// 다중선택이 아닌 경우에만 모달 닫기
if (!multiple) {
onOpenChange(false);
}
};
// 항목이 선택되어 있는지 확인
const isItemSelected = (item: EntitySearchResult): boolean => {
return selectedValues.includes(String(item[valueField]));
};
// 표시할 컬럼 결정
@@ -123,10 +138,16 @@ export function EntitySearchModal({
{/* 검색 결과 테이블 */}
<div className="border rounded-md overflow-hidden">
<div className="overflow-x-auto">
<div className="overflow-x-auto max-h-[400px] overflow-y-auto">
<table className="w-full text-xs sm:text-sm">
<thead className="bg-muted">
<thead className="bg-muted sticky top-0">
<tr>
{/* 다중선택 시 체크박스 컬럼 */}
{multiple && (
<th className="px-4 py-2 text-left font-medium text-muted-foreground w-12">
</th>
)}
{displayColumns.map((col) => (
<th
key={col}
@@ -135,54 +156,72 @@ export function EntitySearchModal({
{col}
</th>
))}
<th className="px-4 py-2 text-left font-medium text-muted-foreground w-24">
</th>
{!multiple && (
<th className="px-4 py-2 text-left font-medium text-muted-foreground w-24">
</th>
)}
</tr>
</thead>
<tbody>
{loading && results.length === 0 ? (
<tr>
<td colSpan={displayColumns.length + 1} className="px-4 py-8 text-center">
<td colSpan={displayColumns.length + (multiple ? 1 : 2)} className="px-4 py-8 text-center">
<Loader2 className="h-6 w-6 animate-spin mx-auto" />
<p className="mt-2 text-muted-foreground"> ...</p>
</td>
</tr>
) : results.length === 0 ? (
<tr>
<td colSpan={displayColumns.length + 1} className="px-4 py-8 text-center text-muted-foreground">
<td colSpan={displayColumns.length + (multiple ? 1 : 2)} className="px-4 py-8 text-center text-muted-foreground">
</td>
</tr>
) : (
results.map((item, index) => {
const uniqueKey = item[valueField] !== undefined ? `${item[valueField]}` : `row-${index}`;
const isSelected = isItemSelected(item);
return (
<tr
<tr
key={uniqueKey}
className="border-t hover:bg-accent cursor-pointer transition-colors"
onClick={() => handleSelect(item)}
>
className={cn(
"border-t cursor-pointer transition-colors",
isSelected ? "bg-blue-50 hover:bg-blue-100" : "hover:bg-accent"
)}
onClick={() => handleSelect(item)}
>
{/* 다중선택 시 체크박스 */}
{multiple && (
<td className="px-4 py-2">
<Checkbox
checked={isSelected}
onCheckedChange={() => handleSelect(item)}
onClick={(e) => e.stopPropagation()}
/>
</td>
)}
{displayColumns.map((col) => (
<td key={`${uniqueKey}-${col}`} className="px-4 py-2">
{item[col] || "-"}
</td>
))}
<td className="px-4 py-2">
<Button
size="sm"
variant="outline"
onClick={(e) => {
e.stopPropagation();
handleSelect(item);
}}
className="h-7 text-xs"
>
</Button>
</td>
</tr>
);
{item[col] || "-"}
</td>
))}
{!multiple && (
<td className="px-4 py-2">
<Button
size="sm"
variant="outline"
onClick={(e) => {
e.stopPropagation();
handleSelect(item);
}}
className="h-7 text-xs"
>
</Button>
</td>
)}
</tr>
);
})
)}
</tbody>
@@ -211,12 +250,18 @@ export function EntitySearchModal({
)}
<DialogFooter className="gap-2 sm:gap-0">
{/* 다중선택 시 선택된 항목 수 표시 */}
{multiple && selectedValues.length > 0 && (
<div className="flex-1 text-sm text-muted-foreground">
{selectedValues.length}
</div>
)}
<Button
variant="outline"
onClick={() => onOpenChange(false)}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
{multiple ? "완료" : "취소"}
</Button>
</DialogFooter>
</DialogContent>