자동완성 검색 입력 컴포넌트 다중 컬럼 표시 기능추가
This commit is contained in:
parent
2cddb42255
commit
93d9937343
|
|
@ -42,10 +42,26 @@ export function AutocompleteSearchInputComponent({
|
||||||
// config prop 우선, 없으면 개별 prop 사용
|
// config prop 우선, 없으면 개별 prop 사용
|
||||||
const tableName = config?.tableName || propTableName || "";
|
const tableName = config?.tableName || propTableName || "";
|
||||||
const displayField = config?.displayField || propDisplayField || "";
|
const displayField = config?.displayField || propDisplayField || "";
|
||||||
|
const displayFields = config?.displayFields || (displayField ? [displayField] : []); // 다중 표시 필드
|
||||||
|
const displaySeparator = config?.displaySeparator || " → "; // 구분자
|
||||||
const valueField = config?.valueField || propValueField || "";
|
const valueField = config?.valueField || propValueField || "";
|
||||||
const searchFields = config?.searchFields || propSearchFields || [displayField];
|
const searchFields = config?.searchFields || propSearchFields || displayFields; // 검색 필드도 다중 표시 필드 사용
|
||||||
const placeholder = config?.placeholder || propPlaceholder || "검색...";
|
const placeholder = config?.placeholder || propPlaceholder || "검색...";
|
||||||
|
|
||||||
|
// 다중 필드 값을 조합하여 표시 문자열 생성
|
||||||
|
const getDisplayValue = (item: EntitySearchResult): string => {
|
||||||
|
if (displayFields.length > 1) {
|
||||||
|
// 여러 필드를 구분자로 조합
|
||||||
|
const values = displayFields
|
||||||
|
.map((field) => item[field])
|
||||||
|
.filter((v) => v !== null && v !== undefined && v !== "")
|
||||||
|
.map((v) => String(v));
|
||||||
|
return values.join(displaySeparator);
|
||||||
|
}
|
||||||
|
// 단일 필드
|
||||||
|
return item[displayField] || "";
|
||||||
|
};
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [selectedData, setSelectedData] = useState<EntitySearchResult | null>(null);
|
const [selectedData, setSelectedData] = useState<EntitySearchResult | null>(null);
|
||||||
|
|
@ -115,7 +131,7 @@ export function AutocompleteSearchInputComponent({
|
||||||
|
|
||||||
const handleSelect = (item: EntitySearchResult) => {
|
const handleSelect = (item: EntitySearchResult) => {
|
||||||
setSelectedData(item);
|
setSelectedData(item);
|
||||||
setInputValue(item[displayField] || "");
|
setInputValue(getDisplayValue(item));
|
||||||
|
|
||||||
console.log("🔍 AutocompleteSearchInput handleSelect:", {
|
console.log("🔍 AutocompleteSearchInput handleSelect:", {
|
||||||
item,
|
item,
|
||||||
|
|
@ -239,7 +255,7 @@ export function AutocompleteSearchInputComponent({
|
||||||
onClick={() => handleSelect(item)}
|
onClick={() => handleSelect(item)}
|
||||||
className="w-full px-3 py-2 text-left text-xs transition-colors hover:bg-accent sm:text-sm"
|
className="w-full px-3 py-2 text-left text-xs transition-colors hover:bg-accent sm:text-sm"
|
||||||
>
|
>
|
||||||
<div className="font-medium">{item[displayField]}</div>
|
<div className="font-medium">{getDisplayValue(item)}</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -184,52 +184,118 @@ export function AutocompleteSearchInputConfigPanel({
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 2. 표시 필드 선택 */}
|
{/* 2. 표시 필드 선택 (다중 선택 가능) */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs font-semibold sm:text-sm">2. 표시 필드 *</Label>
|
<Label className="text-xs font-semibold sm:text-sm">2. 표시 필드 * (여러 개 선택 가능)</Label>
|
||||||
<Popover open={openDisplayFieldCombo} onOpenChange={setOpenDisplayFieldCombo}>
|
<div className="space-y-2">
|
||||||
<PopoverTrigger asChild>
|
{/* 선택된 필드 표시 */}
|
||||||
<Button
|
{(localConfig.displayFields && localConfig.displayFields.length > 0) ? (
|
||||||
variant="outline"
|
<div className="flex flex-wrap gap-1 rounded-md border p-2 min-h-[40px]">
|
||||||
role="combobox"
|
{localConfig.displayFields.map((fieldName) => {
|
||||||
aria-expanded={openDisplayFieldCombo}
|
const col = sourceTableColumns.find((c) => c.columnName === fieldName);
|
||||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
return (
|
||||||
disabled={!localConfig.tableName || isLoadingSourceColumns}
|
<span
|
||||||
>
|
key={fieldName}
|
||||||
{localConfig.displayField
|
className="inline-flex items-center gap-1 rounded-md bg-primary/10 px-2 py-1 text-xs"
|
||||||
? sourceTableColumns.find((c) => c.columnName === localConfig.displayField)?.displayName || localConfig.displayField
|
>
|
||||||
: isLoadingSourceColumns ? "로딩 중..." : "사용자에게 보여줄 필드"}
|
{col?.displayName || fieldName}
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<button
|
||||||
</Button>
|
type="button"
|
||||||
</PopoverTrigger>
|
onClick={() => {
|
||||||
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
const newFields = localConfig.displayFields?.filter((f) => f !== fieldName) || [];
|
||||||
<Command>
|
updateConfig({
|
||||||
<CommandInput placeholder="필드 검색..." className="text-xs sm:text-sm" />
|
displayFields: newFields,
|
||||||
<CommandList>
|
displayField: newFields[0] || "", // 첫 번째 필드를 기본 displayField로
|
||||||
<CommandEmpty className="text-xs sm:text-sm">필드를 찾을 수 없습니다.</CommandEmpty>
|
});
|
||||||
<CommandGroup>
|
|
||||||
{sourceTableColumns.map((column) => (
|
|
||||||
<CommandItem
|
|
||||||
key={column.columnName}
|
|
||||||
value={column.columnName}
|
|
||||||
onSelect={() => {
|
|
||||||
updateConfig({ displayField: column.columnName });
|
|
||||||
setOpenDisplayFieldCombo(false);
|
|
||||||
}}
|
}}
|
||||||
className="text-xs sm:text-sm"
|
className="hover:text-destructive"
|
||||||
>
|
>
|
||||||
<Check className={cn("mr-2 h-4 w-4", localConfig.displayField === column.columnName ? "opacity-100" : "opacity-0")} />
|
<X className="h-3 w-3" />
|
||||||
<div className="flex flex-col">
|
</button>
|
||||||
<span className="font-medium">{column.displayName || column.columnName}</span>
|
</span>
|
||||||
{column.displayName && <span className="text-[10px] text-gray-500">{column.columnName}</span>}
|
);
|
||||||
</div>
|
})}
|
||||||
</CommandItem>
|
</div>
|
||||||
))}
|
) : (
|
||||||
</CommandGroup>
|
<div className="rounded-md border border-dashed p-2 text-center text-xs text-muted-foreground">
|
||||||
</CommandList>
|
아래에서 표시할 필드를 선택하세요
|
||||||
</Command>
|
</div>
|
||||||
</PopoverContent>
|
)}
|
||||||
</Popover>
|
|
||||||
|
{/* 필드 선택 드롭다운 */}
|
||||||
|
<Popover open={openDisplayFieldCombo} onOpenChange={setOpenDisplayFieldCombo}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={openDisplayFieldCombo}
|
||||||
|
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||||
|
disabled={!localConfig.tableName || isLoadingSourceColumns}
|
||||||
|
>
|
||||||
|
{isLoadingSourceColumns ? "로딩 중..." : "필드 추가..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="필드 검색..." className="text-xs sm:text-sm" />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty className="text-xs sm:text-sm">필드를 찾을 수 없습니다.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{sourceTableColumns.map((column) => {
|
||||||
|
const isSelected = localConfig.displayFields?.includes(column.columnName);
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={column.columnName}
|
||||||
|
value={column.columnName}
|
||||||
|
onSelect={() => {
|
||||||
|
const currentFields = localConfig.displayFields || [];
|
||||||
|
let newFields: string[];
|
||||||
|
if (isSelected) {
|
||||||
|
newFields = currentFields.filter((f) => f !== column.columnName);
|
||||||
|
} else {
|
||||||
|
newFields = [...currentFields, column.columnName];
|
||||||
|
}
|
||||||
|
updateConfig({
|
||||||
|
displayFields: newFields,
|
||||||
|
displayField: newFields[0] || "", // 첫 번째 필드를 기본 displayField로
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="text-xs sm:text-sm"
|
||||||
|
>
|
||||||
|
<Check className={cn("mr-2 h-4 w-4", isSelected ? "opacity-100" : "opacity-0")} />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-medium">{column.displayName || column.columnName}</span>
|
||||||
|
{column.displayName && <span className="text-[10px] text-gray-500">{column.columnName}</span>}
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{/* 구분자 설정 */}
|
||||||
|
{localConfig.displayFields && localConfig.displayFields.length > 1 && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Label className="text-xs whitespace-nowrap">구분자:</Label>
|
||||||
|
<Input
|
||||||
|
value={localConfig.displaySeparator || " → "}
|
||||||
|
onChange={(e) => updateConfig({ displaySeparator: e.target.value })}
|
||||||
|
placeholder=" → "
|
||||||
|
className="h-7 w-20 text-xs text-center"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
미리보기: {localConfig.displayFields.map((f) => {
|
||||||
|
const col = sourceTableColumns.find((c) => c.columnName === f);
|
||||||
|
return col?.displayName || f;
|
||||||
|
}).join(localConfig.displaySeparator || " → ")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 3. 저장 대상 테이블 선택 */}
|
{/* 3. 저장 대상 테이블 선택 */}
|
||||||
|
|
@ -419,7 +485,9 @@ export function AutocompleteSearchInputConfigPanel({
|
||||||
<strong>외부 테이블:</strong> {localConfig.tableName}
|
<strong>외부 테이블:</strong> {localConfig.tableName}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>표시 필드:</strong> {localConfig.displayField}
|
<strong>표시 필드:</strong> {localConfig.displayFields?.length
|
||||||
|
? localConfig.displayFields.join(localConfig.displaySeparator || " → ")
|
||||||
|
: localConfig.displayField}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>저장 테이블:</strong> {localConfig.targetTable}
|
<strong>저장 테이블:</strong> {localConfig.targetTable}
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,8 @@ export interface AutocompleteSearchInputConfig {
|
||||||
fieldMappings?: FieldMapping[]; // 매핑할 필드 목록
|
fieldMappings?: FieldMapping[]; // 매핑할 필드 목록
|
||||||
// 저장 대상 테이블 (간소화 버전)
|
// 저장 대상 테이블 (간소화 버전)
|
||||||
targetTable?: string;
|
targetTable?: string;
|
||||||
|
// 🆕 다중 표시 필드 설정 (여러 컬럼 조합)
|
||||||
|
displayFields?: string[]; // 여러 컬럼을 조합하여 표시
|
||||||
|
displaySeparator?: string; // 구분자 (기본값: " - ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue