diff --git a/com.twin.app.shoptime/assets/searchpanel/img-search-nodata.png b/com.twin.app.shoptime/assets/searchpanel/img-search-nodata.png new file mode 100644 index 00000000..217cc276 Binary files /dev/null and b/com.twin.app.shoptime/assets/searchpanel/img-search-nodata.png differ diff --git a/com.twin.app.shoptime/assets/searchpanel/img-search-nodata@2x.png b/com.twin.app.shoptime/assets/searchpanel/img-search-nodata@2x.png new file mode 100644 index 00000000..d40df8b1 Binary files /dev/null and b/com.twin.app.shoptime/assets/searchpanel/img-search-nodata@2x.png differ diff --git a/com.twin.app.shoptime/assets/searchpanel/img-search-nodata@3x.png b/com.twin.app.shoptime/assets/searchpanel/img-search-nodata@3x.png new file mode 100644 index 00000000..45ea920a Binary files /dev/null and b/com.twin.app.shoptime/assets/searchpanel/img-search-nodata@3x.png differ diff --git a/com.twin.app.shoptime/src/components/TGrid/TGrid.jsx b/com.twin.app.shoptime/src/components/TGrid/TGrid.jsx new file mode 100644 index 00000000..27a34355 --- /dev/null +++ b/com.twin.app.shoptime/src/components/TGrid/TGrid.jsx @@ -0,0 +1,19 @@ +import React from "react"; + +import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; + +import css from "./TGrid.module.less"; + +const GridContainer = SpotlightContainerDecorator( + { + leaveFor: { left: "", right: "" }, + enterTo: "last-focused", + }, + "ul" +); + +export default function TGrid({ children }) { + return ( + {children} + ); +} diff --git a/com.twin.app.shoptime/src/components/TGrid/TGrid.module.less b/com.twin.app.shoptime/src/components/TGrid/TGrid.module.less new file mode 100644 index 00000000..3e0d1794 --- /dev/null +++ b/com.twin.app.shoptime/src/components/TGrid/TGrid.module.less @@ -0,0 +1,8 @@ +@import "../../style/CommonStyle.module.less"; +@import "../../style/utils.module.less"; + +.gridContainer { + width: 100%; + .flex(@alignCenter:flex-start); + gap: 15px; +} diff --git a/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx new file mode 100644 index 00000000..eec123d4 --- /dev/null +++ b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx @@ -0,0 +1,61 @@ +import React from "react"; + +import Spottable from "@enact/spotlight/Spottable"; + +import css from "./TItemCard.module.less"; + +const SpottableItemList = Spottable("li"); + +export default function TItemCard({ + children, + imageSource, + imageAlt, + productName, + priceInfo, + ...rest +}) { + const parsePriceInfo = (priceInfo) => { + const priceParts = priceInfo + .split("|") + .filter((part) => part !== "N") + .map((item) => item.trim()); + + let originalPrice, discountedPrice, discountRate; + + if (priceParts.length === 4) { + [originalPrice, discountedPrice, , discountRate] = priceParts; + } else if (priceParts.length === 2) { + [originalPrice, discountedPrice] = priceParts; + discountRate = null; + } else { + originalPrice = null; + discountedPrice = null; + discountRate = null; + } + + return { originalPrice, discountedPrice, discountRate }; + }; + + const { originalPrice, discountedPrice, discountRate } = + parsePriceInfo(priceInfo); + + return ( + +
+ {imageAlt} + {discountRate && ( +
{discountRate}
+ )} +
+
+

{productName}

+

+ {discountedPrice} + {originalPrice !== discountedPrice && ( + {originalPrice} + )} +

+
+
+ ); +} diff --git a/com.twin.app.shoptime/src/components/TItemCard/TItemCard.module.less b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.module.less new file mode 100644 index 00000000..40f2eb9b --- /dev/null +++ b/com.twin.app.shoptime/src/components/TItemCard/TItemCard.module.less @@ -0,0 +1,72 @@ +@import "../../style/CommonStyle.module.less"; +@import "../../style/utils.module.less"; + +.container { + .size(@w: 324px, @h: 438px); + padding: 18px; + border-radius: 12px; + border: 1px solid @COLOR_GRAY02; + background-color: @COLOR_WHITE; + + &:focus-within { + border: 4px solid @PRIMARY_COLOR_RED; + box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.5); + } + + .imageContainer { + width: 288px; + height: 288px; + overflow: hidden; + position: relative; + + img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + } + + .discountBanner { + position: absolute; + .flex(); + width: 60px; + height: 60px; + bottom: 12px; + right: 12px; + background-color: @PRIMARY_COLOR_RED; + color: @COLOR_WHITE; + border-radius: 50%; + padding: 5px 10px; + font-size: 26px; + } + } + + .descContainer { + .productName { + font-family: @baseFontBold; + font-size: 24px; + color: @COLOR_GRAY06; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + margin-top: 14px; + } + + .priceInfo { + margin: 9px 0 12px 0; + font-size: 30px; + font-family: @baseFontBold; + color: @PRIMARY_COLOR_RED; + + .originalPrice { + font-family: @baseFont; + margin-left: 5px; + text-decoration: line-through; + color: @COLOR_GRAY04; + font-size: 18px; + } + } + } +} diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx index 61028e5d..4c462def 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx @@ -17,6 +17,10 @@ import Spotlight from "@enact/spotlight"; import { changeAppStatus } from "../../features/common/commonSlice"; import { getSearch } from "../../features/search/searchSlice"; +import SearchNoDataImage from "../../../assets/searchpanel/img-search-nodata.png"; +import TGrid from "../../components/TGrid/TGrid"; +import TItemCard from "../../components/TItemCard/TItemCard"; + const Container = SpotlightContainerDecorator( { enterTo: "last-focused" }, "div" @@ -34,6 +38,9 @@ export default function SearchPanel() { const searchDatas = useSelector( (state) => state.search.searchDatas.data?.result.results ); + const bestSellerDatas = useSelector( + (state) => state.product.bestSellerData.bestSeller + ); const [currentPage, setCurrentPage] = useState(1); const [paginatedKeywords, setPaginatedKeywords] = useState([]); @@ -69,29 +76,43 @@ export default function SearchPanel() { } }, []); - const handleSearchSubmit = useCallback(async () => { - setSearchPerformed(true); + const performSearch = useCallback( + async (query) => { + if (!query || query.trim() === "") { + setSearchPerformed(false); + return; + } - if (searchQuery.trim() === "") { - setSearchPerformed(false); - return; - } + setSearchPerformed(true); - try { - await dispatch( - getSearch({ - service: "com.lgshop.app", - query: searchQuery, - startIndex: 1, - maxResults: 10, - domain: "theme,show,item", - }) - ).unwrap(); - } catch (error) { - console.error("Search request failed: ", error); - } + try { + await dispatch( + getSearch({ + service: "com.lgshop.app", + query: query, + startIndex: 1, + maxResults: 10, + domain: "theme,show,item", + }) + ).unwrap(); + } catch (error) { + console.error("Search request failed: ", error); + } + }, + [dispatch] + ); + + const handleSearchSubmit = useCallback(() => { + performSearch(searchQuery); }, [searchQuery, dispatch]); + const handleKeywordSearch = useCallback( + (keyword) => { + performSearch(keyword); + }, + [performSearch] + ); + const handleNext = useCallback(() => { setCurrentPage((prev) => prev + 1); setPageChanged(true); @@ -111,7 +132,26 @@ export default function SearchPanel() { if (searchDatas && searchDatas.length > 0) { return
data!!
; } else { - return
SORRY
; + return ( + + No Datas +

{$L("SORRY, NO RESULTS MATCHING YOUR SEARCH")}

+ + {bestSellerDatas && + bestSellerDatas + .slice(0, 5) + .map((bestSeller) => ( + + ))} + +
+ ); } } else { return ( @@ -129,6 +169,7 @@ export default function SearchPanel() { key={index} spotlightId={index === 0 ? "first-keyword-button" : undefined} className={classNames(css.keywordBox)} + onClick={() => handleKeywordSearch(keyword.keywd)} > {keyword.keywd} @@ -145,7 +186,6 @@ export default function SearchPanel() { ); } }; - return (