[251103] fix: TDropDown 필터링 기능 구현 및 복합 이벤트 처리
- 원본 TDropDown에 onChange/onSelect 이벤트 처리 추가 - 로컬 TDropDown(SearchPanel) 생성 및 Spotlight 지원 구현 - width="small" 스타일 및 spotlightId prop 지원 추가 - SearchResults.new.v2.jsx에서 로컬 TDropDown 사용 설정 - sortItems 함수 및 필터 선택 핸들러 연결 이전 필터링 이벤트가 제대로 처리되지 않는 문제를 해결했습니다. Enact Dropdown의 onChange/onSelect 이벤트를 모두 처리하여 필터링 기능이 정상 작동하도록 수정했습니다. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export default memo(function TDropDown({
|
||||
direction = "below",
|
||||
onClose,
|
||||
onOpen,
|
||||
onSelect,
|
||||
scrollTop,
|
||||
selectedIndex,
|
||||
size,
|
||||
@@ -34,6 +35,12 @@ export default memo(function TDropDown({
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
const _onChange = useCallback((event) => {
|
||||
if (onSelect) {
|
||||
onSelect({ selected: event.selected });
|
||||
}
|
||||
}, [onSelect]);
|
||||
|
||||
return (
|
||||
<DropDown
|
||||
className={classNames(
|
||||
@@ -45,6 +52,8 @@ export default memo(function TDropDown({
|
||||
)}
|
||||
direction={direction}
|
||||
selected={selectedIndex}
|
||||
onChange={_onChange}
|
||||
onSelect={_onChange}
|
||||
onFocus={handleScrollReset}
|
||||
onBlur={handleStopScrolling}
|
||||
onOpen={_onOpen}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { pushPanel } from '../../actions/panelActions';
|
||||
import { hideShopperHouseError } from '../../actions/searchActions';
|
||||
import CustomImage from '../../components/CustomImage/CustomImage';
|
||||
import TButtonTab, { LIST_TYPE } from '../../components/TButtonTab/TButtonTab';
|
||||
import TDropDown from '../../components/TDropDown/TDropDown';
|
||||
import TDropDown from './components/TDropDown/TDropDown';
|
||||
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
|
||||
import { panel_names } from '../../utils/Config';
|
||||
import { $L } from '../../utils/helperMethods';
|
||||
@@ -193,6 +193,30 @@ const SearchResultsNew = ({
|
||||
onSearchInputFocus = false,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// ✅ State 정의를 먼저 선언 (hoisting 문제 해결)
|
||||
//탭
|
||||
const [tab, setTab] = useState(panelInfo?.tab ? panelInfo?.tab : 0);
|
||||
//드롭다운
|
||||
const [dropDownTab, setDropDownTab] = useState(0); // 기본값: 첫번째 옵션(API sortingType)
|
||||
//표시할 아이템 개수
|
||||
const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE);
|
||||
const [styleChange, setStyleChange] = useState(false);
|
||||
|
||||
// HowAboutThese 모드 상태 관리
|
||||
const [howAboutTheseMode, setHowAboutTheseMode] = useState(HOW_ABOUT_THESE_MODES.SMALL);
|
||||
// HowAboutThese Response 로딩 상태
|
||||
const [isHowAboutTheseLoading, setIsHowAboutTheseLoading] = useState(false);
|
||||
const [hasPendingResponse, setHasPendingResponse] = useState(false);
|
||||
const previousShopperHouseInfoRef = useRef(shopperHouseInfo);
|
||||
|
||||
const localChangePageRef = useRef(null);
|
||||
const effectiveChangePageRef = cbChangePageRef ?? localChangePageRef;
|
||||
const [pendingFocusIndex, setPendingFocusIndex] = useState(null);
|
||||
|
||||
// ShopperHouse 에러 팝업 상태 가져오기
|
||||
const shopperHouseErrorPopup = useSelector((state) => state.search.shopperHouseErrorPopup);
|
||||
|
||||
// ShopperHouse 데이터를 ItemCard 형식으로 변환
|
||||
const convertedShopperHouseItems = useMemo(() => {
|
||||
// 🎯 Fallback 로직: HowAboutThese 로딩 중에만 fallbackShopperHouseData 허용
|
||||
@@ -231,7 +255,7 @@ const SearchResultsNew = ({
|
||||
rangeType: resultData.rangeType || '',
|
||||
};
|
||||
});
|
||||
}, [shopperHouseInfo, fallbackShopperHouseData]);
|
||||
}, [shopperHouseInfo, fallbackShopperHouseData, isHowAboutTheseLoading]);
|
||||
|
||||
const getButtonTabList = () => {
|
||||
// ShopperHouse 데이터가 있으면 그것을 사용, 없으면 기존 검색 결과 사용
|
||||
@@ -244,15 +268,6 @@ const SearchResultsNew = ({
|
||||
].filter(Boolean);
|
||||
};
|
||||
|
||||
//탭
|
||||
const [tab, setTab] = useState(panelInfo?.tab ? panelInfo?.tab : 0);
|
||||
//드롭다운
|
||||
const [dropDownTab, setDropDownTab] = useState(0); // 기본값: 첫번째 옵션(API sortingType)
|
||||
//표시할 아이템 개수
|
||||
const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE);
|
||||
|
||||
const [styleChange, setStyleChange] = useState(false);
|
||||
|
||||
// 동적 정렬 옵션 배열 (인덱스 기반)
|
||||
const filterOptions = useMemo(() => {
|
||||
const apiSortingType = convertedShopperHouseItems?.[0]?.sortingType;
|
||||
@@ -268,7 +283,7 @@ const SearchResultsNew = ({
|
||||
];
|
||||
}, [convertedShopperHouseItems]);
|
||||
|
||||
// UI에 표시되는 라벨만 추출
|
||||
// UI에 표시되는 라벨만 추출 (문자열 배열)
|
||||
const filterMethods = useMemo(() =>
|
||||
filterOptions.map(opt => $L(opt.label)),
|
||||
[filterOptions]
|
||||
@@ -294,20 +309,7 @@ const SearchResultsNew = ({
|
||||
setVisibleCount(ITEMS_PER_PAGE); // 표시 개수도 리셋
|
||||
}, [shopperHouseInfo?.results?.[0]?.searchId]);
|
||||
|
||||
const localChangePageRef = useRef(null);
|
||||
const effectiveChangePageRef = cbChangePageRef ?? localChangePageRef;
|
||||
const [pendingFocusIndex, setPendingFocusIndex] = useState(null);
|
||||
|
||||
// HowAboutThese 모드 상태 관리
|
||||
const [howAboutTheseMode, setHowAboutTheseMode] = useState(HOW_ABOUT_THESE_MODES.SMALL);
|
||||
|
||||
// HowAboutThese Response 로딩 상태
|
||||
const [isHowAboutTheseLoading, setIsHowAboutTheseLoading] = useState(false);
|
||||
const [hasPendingResponse, setHasPendingResponse] = useState(false);
|
||||
const previousShopperHouseInfoRef = useRef(shopperHouseInfo);
|
||||
|
||||
// ShopperHouse 에러 팝업 상태 가져오기
|
||||
const shopperHouseErrorPopup = useSelector((state) => state.search.shopperHouseErrorPopup);
|
||||
// ✅ 이미 위에서 정의됨 (중복 제거)
|
||||
|
||||
// 자동 닫기 타이머 (3초 후 자동으로 팝업 닫기)
|
||||
useEffect(() => {
|
||||
@@ -430,6 +432,15 @@ const SearchResultsNew = ({
|
||||
// 현재 탭의 데이터 가져오기 - ShopperHouse 데이터 우선 (정렬된 데이터 사용)
|
||||
const hasShopperHouseItems = convertedShopperHouseItems?.length > 0;
|
||||
const currentData = tab === 0 ? convertedShopperHouseItemsSorted || itemInfo : showInfo;
|
||||
|
||||
console.log('[SearchResultsNew] 📊 렌더링 상태 체크:');
|
||||
console.log(' - tab:', tab);
|
||||
console.log(' - hasShopperHouseItems:', hasShopperHouseItems);
|
||||
console.log(' - convertedShopperHouseItems?.length:', convertedShopperHouseItems?.length);
|
||||
console.log(' - filterOptions:', filterOptions);
|
||||
console.log(' - filterMethods:', filterMethods);
|
||||
console.log(' - dropDownTab:', dropDownTab);
|
||||
console.log(' - TDropDown 렌더링 조건 (tab === 0 && hasShopperHouseItems):', tab === 0 && hasShopperHouseItems);
|
||||
|
||||
// 표시할 데이터 (처음부터 visibleCount 개수만큼)
|
||||
const displayedData = useMemo(() => {
|
||||
@@ -467,16 +478,28 @@ const SearchResultsNew = ({
|
||||
//필터선택
|
||||
const handleSelectFilter = useCallback(
|
||||
({ selected }) => {
|
||||
console.log('[SearchResultsNew] 🔔 handleSelectFilter 호출됨!');
|
||||
console.log('[SearchResultsNew] - 전달받은 selected:', selected);
|
||||
console.log('[SearchResultsNew] - 현재 dropDownTab:', dropDownTab);
|
||||
console.log('[SearchResultsNew] - filterOptions:', filterOptions);
|
||||
|
||||
if (selected === dropDownTab) {
|
||||
console.log('[SearchResultsNew] ⏭️ 동일한 정렬 옵션 선택 무시 - selected:', selected);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[SearchResultsNew] 🎯 정렬 옵션 변경 -', dropDownTab, '→', selected, '(', SORTING_TYPE_MAP[selected], ')');
|
||||
const selectedOption = filterOptions[selected];
|
||||
console.log('[SearchResultsNew] 🎯 정렬 옵션 변경');
|
||||
console.log('[SearchResultsNew] - 이전:', dropDownTab, '→ 새로운:', selected);
|
||||
console.log('[SearchResultsNew] - 선택된 옵션:', selectedOption);
|
||||
console.log('[SearchResultsNew] - 정렬 타입:', selectedOption?.value);
|
||||
|
||||
setDropDownTab(selected);
|
||||
setVisibleCount(ITEMS_PER_PAGE); // 필터 변경시 표시 개수 리셋
|
||||
|
||||
console.log('[SearchResultsNew] ✅ dropDownTab 상태 업데이트 완료');
|
||||
},
|
||||
[dropDownTab]
|
||||
[dropDownTab, filterOptions]
|
||||
);
|
||||
|
||||
// Spottable 컴포넌트 캐싱하여 메모리 누수 방지
|
||||
@@ -736,7 +759,10 @@ const SearchResultsNew = ({
|
||||
onOpen={handleStyle}
|
||||
onClose={handleStyleOut}
|
||||
selectedIndex={dropDownTab}
|
||||
size="small"
|
||||
width="small"
|
||||
spotlightId="sortingDropdown"
|
||||
data-spotlight-up={SpotlightIds.SEARCH_TAB_CONTAINER}
|
||||
>
|
||||
{filterMethods}
|
||||
</TDropDown>
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import React, { memo, useCallback } from "react";
|
||||
|
||||
import classNames from "classnames";
|
||||
|
||||
import DropDown from "@enact/sandstone/Dropdown";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import { countryCode } from "../../../../api/apiConfig";
|
||||
import useScrollReset from "../../../../hooks/useScrollReset";
|
||||
import css from "./TDropDown.module.less";
|
||||
|
||||
const SpottableDropDown = Spottable(DropDown);
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
export default memo(function TDropDown({
|
||||
children,
|
||||
className,
|
||||
color,
|
||||
direction = "below",
|
||||
onClose,
|
||||
onOpen,
|
||||
onSelect,
|
||||
scrollTop,
|
||||
selectedIndex,
|
||||
size,
|
||||
spotlightId,
|
||||
width,
|
||||
...rest
|
||||
}) {
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollTop);
|
||||
|
||||
const _onOpen = useCallback(() => {
|
||||
if (onOpen) {
|
||||
onOpen();
|
||||
}
|
||||
}, [onOpen]);
|
||||
|
||||
const _onClose = useCallback(() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
const _onChange = useCallback((event) => {
|
||||
console.log('[TDropDown LOCAL] 🔥 onChange 이벤트 발생!');
|
||||
console.log('[TDropDown LOCAL] event:', event);
|
||||
console.log('[TDropDown LOCAL] event.selected:', event?.selected);
|
||||
console.log('[TDropDown LOCAL] event.value:', event?.value);
|
||||
console.log('[TDropDown LOCAL] onSelect 콜백:', !!onSelect);
|
||||
if (onSelect) {
|
||||
console.log('[TDropDown LOCAL] ✅ onSelect 호출:', { selected: event.selected });
|
||||
onSelect({ selected: event.selected });
|
||||
}
|
||||
}, [onSelect]);
|
||||
|
||||
console.log('[TDropDown LOCAL] ⚠️ TDropDown 렌더링 - selectedIndex:', selectedIndex, 'onSelect:', !!onSelect, 'children:', Array.isArray(children) ? children.length : typeof children);
|
||||
|
||||
// Spotlight을 사용하지 않는 버전 (필터링 테스트용)
|
||||
if (spotlightId) {
|
||||
return (
|
||||
<Container spotlightId={spotlightId}>
|
||||
<SpottableDropDown
|
||||
className={classNames(
|
||||
css.tDropdown,
|
||||
css[size],
|
||||
css[color],
|
||||
(selectedIndex !== undefined && selectedIndex !== null) && css.selected,
|
||||
className
|
||||
)}
|
||||
direction={direction}
|
||||
selected={selectedIndex}
|
||||
onChange={_onChange}
|
||||
onSelect={_onChange}
|
||||
onFocus={handleScrollReset}
|
||||
onBlur={handleStopScrolling}
|
||||
onOpen={_onOpen}
|
||||
onClose={_onClose}
|
||||
style={width === 'small' ? { width: '240px', minWidth: '240px' } :
|
||||
width === 'medium' ? { width: '320px', minWidth: '320px' } :
|
||||
width === 'large' ? { width: '400px', minWidth: '400px' } : undefined}
|
||||
aria-disabled={countryCode && countryCode !== "US"}
|
||||
aria-label={children}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</SpottableDropDown>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
// Spotlight 없이 기본 DropDown 사용
|
||||
return (
|
||||
<DropDown
|
||||
className={classNames(
|
||||
css.tDropdown,
|
||||
css[size],
|
||||
css[color],
|
||||
(selectedIndex !== undefined && selectedIndex !== null) && css.selected,
|
||||
className
|
||||
)}
|
||||
direction={direction}
|
||||
selected={selectedIndex}
|
||||
onChange={_onChange}
|
||||
onSelect={_onChange}
|
||||
onFocus={handleScrollReset}
|
||||
onBlur={handleStopScrolling}
|
||||
onOpen={_onOpen}
|
||||
onClose={_onClose}
|
||||
style={width === 'small' ? { width: '240px', minWidth: '240px' } :
|
||||
width === 'medium' ? { width: '320px', minWidth: '320px' } :
|
||||
width === 'large' ? { width: '400px', minWidth: '400px' } : undefined}
|
||||
aria-disabled={countryCode && countryCode !== "US"}
|
||||
aria-label={children}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</DropDown>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
@import "../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../style/utils.module.less";
|
||||
|
||||
.tDropdown {
|
||||
margin-left: 0px !important;
|
||||
margin-right: 0px !important;
|
||||
|
||||
[role="button"] {
|
||||
height: 60px !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 20px 19px !important;
|
||||
font-weight: bold;
|
||||
font-size: 24px !important;
|
||||
background-color: @COLOR_WHITE !important;
|
||||
color: @PRIMARY_COLOR_RED !important;
|
||||
margin: 0 !important;
|
||||
|
||||
> div:first-child {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
> div:nth-child(2) > div:nth-child(1) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 10px !important;
|
||||
font-size: 24px !important;
|
||||
width: 24px !important;
|
||||
color: @PRIMARY_COLOR_RED !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: @PRIMARY_COLOR_RED !important;
|
||||
color: @COLOR_WHITE !important;
|
||||
|
||||
> div:nth-child(2) > div:nth-child(1) {
|
||||
color: @COLOR_WHITE !important;
|
||||
}
|
||||
}
|
||||
&.selected {
|
||||
background-color: @PRIMARY_COLOR_RED !important;
|
||||
color: @COLOR_WHITE !important;
|
||||
|
||||
> div:nth-child(2) > div:nth-child(1) {
|
||||
color: @COLOR_WHITE !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.small {
|
||||
width: 240px !important;
|
||||
min-width: 240px !important;
|
||||
}
|
||||
|
||||
.medium {
|
||||
width: 320px !important;
|
||||
min-width: 320px !important;
|
||||
}
|
||||
|
||||
.large {
|
||||
width: 400px !important;
|
||||
min-width: 400px !important;
|
||||
}
|
||||
|
||||
[id="floatLayer"] {
|
||||
--list-background-color: @COLOR_GRAY03;
|
||||
--list-item-font-color: @COLOR_WHITE;
|
||||
--list-item-focus-background-color: @PRIMARY_COLOR_RED;
|
||||
--list-item-focus-font-color: @COLOR_WHITE;
|
||||
--list-item-select-border-color: @PRIMARY_COLOR_RED;
|
||||
--scroll-track-color: @COLOR_GRAY03;
|
||||
--scroll-bar-color: @COLOR_GRAY04;
|
||||
|
||||
> div > div[id] > div:nth-child(2) {
|
||||
bottom: unset !important;
|
||||
transform: unset !important;
|
||||
|
||||
> div:nth-child(1) {
|
||||
//list layout
|
||||
padding: 0 !important;
|
||||
margin-top: 4px !important;
|
||||
box-shadow: none !important;
|
||||
background-color: var(--list-background-color) !important;
|
||||
border-radius: 6px !important;
|
||||
|
||||
> div {
|
||||
padding: 0 !important;
|
||||
padding-top: 14px !important;
|
||||
height: auto !important;
|
||||
> div > div {
|
||||
> div {
|
||||
padding: 0 15px !important;
|
||||
> div[aria-checked="true"] {
|
||||
background-color: @COLOR_WHITE !important;
|
||||
border: 2px solid @PRIMARY_COLOR_RED;
|
||||
> div:nth-child(2) {
|
||||
> div {
|
||||
> div {
|
||||
color: @PRIMARY_COLOR_RED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
background-color: var(
|
||||
--list-item-focus-background-color
|
||||
) !important;
|
||||
color: @COLOR_WHITE !important;
|
||||
}
|
||||
|
||||
> div:nth-child(1) {
|
||||
font-weight: bold;
|
||||
color: @COLOR_WHITE;
|
||||
font-size: 24px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
height: 63px !important;
|
||||
line-height: 63px !important;
|
||||
-webkit-margin-start: 0 !important;
|
||||
-webkit-inline-start: 0 !important;
|
||||
|
||||
> div:first-child {
|
||||
//list item shadow
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
> div:nth-child(3) {
|
||||
//check icon
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div:nth-child(2) > div {
|
||||
// scroll track
|
||||
background-color: var(--scroll-track-color) !important;
|
||||
width: 4px !important;
|
||||
border-radius: 20px !important;
|
||||
|
||||
> div {
|
||||
//scroll bar
|
||||
background: var(--scroll-bar-color) !important;
|
||||
width: 100% !important;
|
||||
border-radius: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
> div > div {
|
||||
> div {
|
||||
> div[aria-checked="true"] {
|
||||
&:focus {
|
||||
> div:nth-child(2) {
|
||||
> div {
|
||||
> div {
|
||||
color: @COLOR_WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user