feat: select-basic 컴포넌트에 다중선택 기능 추가
기능: - 설정 패널에 '다중 선택' 체크박스 추가 - multiple 옵션 활성화 시 다중선택 UI 렌더링 - 선택된 항목을 태그 형식으로 표시 - 각 태그에 X 버튼으로 개별 제거 가능 - 드롭다운에 체크박스 표시 - 콤마(,) 구분자로 값 저장/파싱 수정사항: - SelectBasicConfigPanel: 다중 선택 체크박스 추가 - SelectBasicConfigPanel: config 병합 방식으로 변경 (다른 속성 보호) - SelectBasicComponent: 초기값 콤마 구분자로 파싱 - SelectBasicComponent: 외부 value 변경 시 다중선택 배열 동기화 - SelectBasicComponent: 다중선택 UI 렌더링 로직 추가 사용법: 1. 설정 패널에서 '다중 선택' 체크 2. 드롭다운에서 여러 항목 선택 3. 선택된 항목이 태그로 표시되며 X로 제거 가능 4. 저장 시 '값1,값2,값3' 형식으로 저장
This commit is contained in:
parent
3219015a39
commit
c57e0218fe
|
|
@ -62,8 +62,14 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
|||
const [selectedValue, setSelectedValue] = useState(externalValue || config?.value || "");
|
||||
const [selectedLabel, setSelectedLabel] = useState("");
|
||||
|
||||
// multiselect의 경우 배열로 관리
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
||||
// multiselect의 경우 배열로 관리 (콤마 구분자로 파싱)
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(() => {
|
||||
const initialValue = externalValue || config?.value || "";
|
||||
if (config?.multiple && typeof initialValue === "string" && initialValue) {
|
||||
return initialValue.split(",").map(v => v.trim()).filter(v => v);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// autocomplete의 경우 검색어 관리
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
|
@ -116,8 +122,14 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
|||
// 값이 실제로 다른 경우에만 업데이트 (빈 문자열도 유효한 값으로 처리)
|
||||
if (newValue !== selectedValue) {
|
||||
setSelectedValue(newValue);
|
||||
|
||||
// 다중선택 모드인 경우 selectedValues도 업데이트
|
||||
if (config?.multiple && typeof newValue === "string" && newValue) {
|
||||
const values = newValue.split(",").map(v => v.trim()).filter(v => v);
|
||||
setSelectedValues(values);
|
||||
}
|
||||
}
|
||||
}, [externalValue, config?.value]);
|
||||
}, [externalValue, config?.value, config?.multiple]);
|
||||
|
||||
// ✅ React Query가 자동으로 처리하므로 복잡한 전역 상태 관리 제거
|
||||
// - 캐싱: React Query가 자동 관리 (10분 staleTime, 30분 gcTime)
|
||||
|
|
@ -500,6 +512,93 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
|||
}
|
||||
|
||||
// select (기본 선택박스)
|
||||
// 다중선택 모드인 경우
|
||||
if (config?.multiple) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div
|
||||
className={cn(
|
||||
"box-border flex h-full min-h-[40px] w-full flex-wrap gap-2 rounded-lg border border-gray-300 bg-white px-3 py-2",
|
||||
!isDesignMode && "hover:border-orange-400",
|
||||
isSelected && "ring-2 ring-orange-500",
|
||||
)}
|
||||
onClick={() => !isDesignMode && setIsOpen(true)}
|
||||
style={{ pointerEvents: isDesignMode ? "none" : "auto" }}
|
||||
>
|
||||
{selectedValues.map((val, idx) => {
|
||||
const opt = allOptions.find((o) => o.value === val);
|
||||
return (
|
||||
<span key={idx} className="flex items-center gap-1 rounded bg-blue-100 px-2 py-1 text-sm text-blue-800">
|
||||
{opt?.label || val}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const newVals = selectedValues.filter((v) => v !== val);
|
||||
setSelectedValues(newVals);
|
||||
const newValue = newVals.join(",");
|
||||
if (isInteractive && onFormDataChange && component.columnName) {
|
||||
onFormDataChange(component.columnName, newValue);
|
||||
}
|
||||
}}
|
||||
className="ml-1 text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{selectedValues.length === 0 && (
|
||||
<span className="text-gray-500">{placeholder}</span>
|
||||
)}
|
||||
</div>
|
||||
{isOpen && !isDesignMode && (
|
||||
<div className="absolute z-[99999] mt-1 max-h-60 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
|
||||
{isLoadingCodes ? (
|
||||
<div className="bg-white px-3 py-2 text-gray-900">로딩 중...</div>
|
||||
) : allOptions.length > 0 ? (
|
||||
allOptions.map((option, index) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
return (
|
||||
<div
|
||||
key={`${option.value}-${index}`}
|
||||
className={cn(
|
||||
"cursor-pointer px-3 py-2 text-gray-900 hover:bg-gray-100",
|
||||
isSelected && "bg-blue-50 font-medium"
|
||||
)}
|
||||
onClick={() => {
|
||||
const newVals = isSelected
|
||||
? selectedValues.filter((v) => v !== option.value)
|
||||
: [...selectedValues, option.value];
|
||||
setSelectedValues(newVals);
|
||||
const newValue = newVals.join(",");
|
||||
if (isInteractive && onFormDataChange && component.columnName) {
|
||||
onFormDataChange(component.columnName, newValue);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => {}}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span>{option.label || option.value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="bg-white px-3 py-2 text-gray-900">옵션이 없습니다</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 단일선택 모드
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ export const SelectBasicConfigPanel: React.FC<SelectBasicConfigPanelProps> = ({
|
|||
onChange,
|
||||
}) => {
|
||||
const handleChange = (key: keyof SelectBasicConfig, value: any) => {
|
||||
onChange({ [key]: value });
|
||||
// 기존 config와 병합하여 전체 객체 전달 (다른 속성 보호)
|
||||
const newConfig = { ...config, [key]: value };
|
||||
onChange(newConfig);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -67,6 +69,15 @@ export const SelectBasicConfigPanel: React.FC<SelectBasicConfigPanelProps> = ({
|
|||
onCheckedChange={(checked) => handleChange("readonly", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="multiple">다중 선택</Label>
|
||||
<Checkbox
|
||||
id="multiple"
|
||||
checked={config.multiple || false}
|
||||
onCheckedChange={(checked) => handleChange("multiple", checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue