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 (
+
+
+

+ {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 (
+
+
+ {$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 (