엔티티타입 입력 셀렉트박스 다중선택 기능
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user