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 { 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 <div>data!!</div>;
|
||||
} 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 {
|
||||
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}
|
||||
</TButton>
|
||||
@@ -145,7 +186,6 @@ export default function SearchPanel() {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TPanel className={css.panel}>
|
||||
<TInput
|
||||
|
||||
Reference in New Issue
Block a user