TGrid, TItemCard 제작 / SearchPanel style 수정 필요
This commit is contained in:
BIN
com.twin.app.shoptime/assets/searchpanel/img-search-nodata.png
Normal file
BIN
com.twin.app.shoptime/assets/searchpanel/img-search-nodata.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
19
com.twin.app.shoptime/src/components/TGrid/TGrid.jsx
Normal file
19
com.twin.app.shoptime/src/components/TGrid/TGrid.jsx
Normal file
@@ -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 (
|
||||||
|
<GridContainer className={css.gridContainer}>{children}</GridContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@import "../../style/CommonStyle.module.less";
|
||||||
|
@import "../../style/utils.module.less";
|
||||||
|
|
||||||
|
.gridContainer {
|
||||||
|
width: 100%;
|
||||||
|
.flex(@alignCenter:flex-start);
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
61
com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx
Normal file
61
com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx
Normal file
@@ -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 (
|
||||||
|
<SpottableItemList {...rest} className={css.container}>
|
||||||
|
<div className={css.imageContainer}>
|
||||||
|
<img src={imageSource} alt={imageAlt} />
|
||||||
|
{discountRate && (
|
||||||
|
<div className={css.discountBanner}>{discountRate}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={css.descContainer}>
|
||||||
|
<h1 className={css.productName}>{productName}</h1>
|
||||||
|
<p className={css.priceInfo}>
|
||||||
|
{discountedPrice}
|
||||||
|
{originalPrice !== discountedPrice && (
|
||||||
|
<span className={css.originalPrice}>{originalPrice}</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</SpottableItemList>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@ import Spotlight from "@enact/spotlight";
|
|||||||
import { changeAppStatus } from "../../features/common/commonSlice";
|
import { changeAppStatus } from "../../features/common/commonSlice";
|
||||||
import { getSearch } from "../../features/search/searchSlice";
|
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(
|
const Container = SpotlightContainerDecorator(
|
||||||
{ enterTo: "last-focused" },
|
{ enterTo: "last-focused" },
|
||||||
"div"
|
"div"
|
||||||
@@ -34,6 +38,9 @@ export default function SearchPanel() {
|
|||||||
const searchDatas = useSelector(
|
const searchDatas = useSelector(
|
||||||
(state) => state.search.searchDatas.data?.result.results
|
(state) => state.search.searchDatas.data?.result.results
|
||||||
);
|
);
|
||||||
|
const bestSellerDatas = useSelector(
|
||||||
|
(state) => state.product.bestSellerData.bestSeller
|
||||||
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [paginatedKeywords, setPaginatedKeywords] = useState([]);
|
const [paginatedKeywords, setPaginatedKeywords] = useState([]);
|
||||||
@@ -69,29 +76,43 @@ export default function SearchPanel() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSearchSubmit = useCallback(async () => {
|
const performSearch = useCallback(
|
||||||
setSearchPerformed(true);
|
async (query) => {
|
||||||
|
if (!query || query.trim() === "") {
|
||||||
|
setSearchPerformed(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (searchQuery.trim() === "") {
|
setSearchPerformed(true);
|
||||||
setSearchPerformed(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await dispatch(
|
await dispatch(
|
||||||
getSearch({
|
getSearch({
|
||||||
service: "com.lgshop.app",
|
service: "com.lgshop.app",
|
||||||
query: searchQuery,
|
query: query,
|
||||||
startIndex: 1,
|
startIndex: 1,
|
||||||
maxResults: 10,
|
maxResults: 10,
|
||||||
domain: "theme,show,item",
|
domain: "theme,show,item",
|
||||||
})
|
})
|
||||||
).unwrap();
|
).unwrap();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Search request failed: ", error);
|
console.error("Search request failed: ", error);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSearchSubmit = useCallback(() => {
|
||||||
|
performSearch(searchQuery);
|
||||||
}, [searchQuery, dispatch]);
|
}, [searchQuery, dispatch]);
|
||||||
|
|
||||||
|
const handleKeywordSearch = useCallback(
|
||||||
|
(keyword) => {
|
||||||
|
performSearch(keyword);
|
||||||
|
},
|
||||||
|
[performSearch]
|
||||||
|
);
|
||||||
|
|
||||||
const handleNext = useCallback(() => {
|
const handleNext = useCallback(() => {
|
||||||
setCurrentPage((prev) => prev + 1);
|
setCurrentPage((prev) => prev + 1);
|
||||||
setPageChanged(true);
|
setPageChanged(true);
|
||||||
@@ -111,7 +132,26 @@ export default function SearchPanel() {
|
|||||||
if (searchDatas && searchDatas.length > 0) {
|
if (searchDatas && searchDatas.length > 0) {
|
||||||
return <div>data!!</div>;
|
return <div>data!!</div>;
|
||||||
} else {
|
} else {
|
||||||
return <div>SORRY</div>;
|
return (
|
||||||
|
<Container>
|
||||||
|
<img src={SearchNoDataImage} alt="No Datas" />
|
||||||
|
<p>{$L("SORRY, NO RESULTS MATCHING YOUR SEARCH")}</p>
|
||||||
|
<TGrid>
|
||||||
|
{bestSellerDatas &&
|
||||||
|
bestSellerDatas
|
||||||
|
.slice(0, 5)
|
||||||
|
.map((bestSeller) => (
|
||||||
|
<TItemCard
|
||||||
|
key={bestSeller.rankOrd}
|
||||||
|
imageSource={bestSeller.imgUrl}
|
||||||
|
imageAlt={bestSeller.prdtNm}
|
||||||
|
productName={bestSeller.prdtNm}
|
||||||
|
priceInfo={bestSeller.priceInfo}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TGrid>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
@@ -129,6 +169,7 @@ export default function SearchPanel() {
|
|||||||
key={index}
|
key={index}
|
||||||
spotlightId={index === 0 ? "first-keyword-button" : undefined}
|
spotlightId={index === 0 ? "first-keyword-button" : undefined}
|
||||||
className={classNames(css.keywordBox)}
|
className={classNames(css.keywordBox)}
|
||||||
|
onClick={() => handleKeywordSearch(keyword.keywd)}
|
||||||
>
|
>
|
||||||
{keyword.keywd}
|
{keyword.keywd}
|
||||||
</TButton>
|
</TButton>
|
||||||
@@ -145,7 +186,6 @@ export default function SearchPanel() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TPanel className={css.panel}>
|
<TPanel className={css.panel}>
|
||||||
<TInput
|
<TInput
|
||||||
|
|||||||
Reference in New Issue
Block a user