[상품 상세] 리뷰 및 디테일 변경건.#1
- 디테일부분에 대한 figma 부분과 동일하게 스타일 처리. - 리뷰부분 및 리뷰 팝업 부분 처리중
This commit is contained in:
@@ -39,7 +39,7 @@ import ShowUserReviews from "../../UserReview/ShowUserReviews";
|
||||
// CSS imports
|
||||
// import infoCSS from "../ProductInfoSection/ProductInfoSection.module.less";
|
||||
// import contentCSS from "../ProductContentSection/ProductContentSection.module.less";
|
||||
import css from "./ProductAllSection.module.less";
|
||||
import css from './ProductAllSection.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{
|
||||
@@ -292,7 +292,7 @@ export default function ProductAllSection({
|
||||
<div className={css.leftInfoWrapper}>
|
||||
<div className={css.headerContent}>
|
||||
<ProductTag productInfo={productData} />
|
||||
{revwGrd && (
|
||||
{revwGrd && revwGrd !== "0.0" && (
|
||||
<StarRating
|
||||
rating={revwGrd}
|
||||
aria-label={"star rating " + revwGrd + " out of 5"}
|
||||
@@ -309,7 +309,11 @@ export default function ProductAllSection({
|
||||
productType={productType}
|
||||
>
|
||||
<div className={css.qrWrapper}>
|
||||
<QRCode productInfo={productData} productType={productType} />
|
||||
<QRCode
|
||||
productInfo={productData}
|
||||
productType={productType}
|
||||
kind={"detail"}
|
||||
/>
|
||||
</div>
|
||||
</ProductOverview>
|
||||
|
||||
@@ -332,6 +336,7 @@ export default function ProductAllSection({
|
||||
selectedPrdtId={panelInfo && panelInfo.prdtId}
|
||||
favoriteFlag={favoriteFlag}
|
||||
onFavoriteFlagChanged={onFavoriteFlagChanged}
|
||||
kind={"item_detail"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -35,13 +35,16 @@
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
width: 645px;
|
||||
width: 650px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
> div {
|
||||
height: 339px;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
|
||||
@@ -49,7 +52,7 @@
|
||||
position: absolute;
|
||||
left: 705px; // 60px + 645px
|
||||
top: 0;
|
||||
width: 1200px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
||||
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -88,7 +91,9 @@
|
||||
// gap 대신 margin 사용 (Chromium 68 호환성)
|
||||
> * {
|
||||
margin-bottom: 5px;
|
||||
&:last-child { margin-bottom: 0; }
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +120,6 @@
|
||||
align-items: flex-start;
|
||||
position: relative; // 자식 absolute 요소의 기준점
|
||||
|
||||
|
||||
// 스크롤러 오버라이드 (1210px = 30px + content + 스크롤바)
|
||||
.scrollerOverride {
|
||||
width: 1210px; // 절대 크기 지정
|
||||
@@ -139,17 +143,16 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #9C9C9C; // 스크롤바 색상
|
||||
background: #9c9c9c; // 스크롤바 색상
|
||||
border-radius: 3px; // 스크롤바 둥근 모서리
|
||||
}
|
||||
|
||||
// 스크롤바 thumb에 hover 효과 적용
|
||||
// 스크롤바 thumb에 hover 효과 적용
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background: #C72054;
|
||||
background: #c72054;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 내부 콘텐츠는 별도 너비 계산 없이 100%를 사용
|
||||
> div {
|
||||
width: 100%; // 부모의 패딩을 제외한 나머지 공간(1114px)을 모두 사용
|
||||
@@ -229,7 +232,6 @@
|
||||
|
||||
// (중복 제거됨) 최상위 스크롤러/섹션 정의는 .scrollerWrapper 중첩 내부로 이동
|
||||
|
||||
|
||||
// ProductDetailCard 스타일 참고 - 크기/간격만 적용
|
||||
|
||||
// 헤더 컨텐츠 영역 (태그, 별점)
|
||||
@@ -237,7 +239,6 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
// 모바일 쇼핑 섹션 (mobileSection 참고)
|
||||
@@ -250,7 +251,9 @@
|
||||
|
||||
> * {
|
||||
margin-right: 6px;
|
||||
&:last-child { margin-right: 0; }
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,14 +261,489 @@
|
||||
flex: 1 1 0 !important;
|
||||
width: auto !important; // flex로 크기 조정
|
||||
height: 60px !important;
|
||||
background: rgba(68, 68, 68, 0.50) !important;
|
||||
background: rgba(68, 68, 68, 0.5) !important;
|
||||
border-radius: 6px !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
margin: 0;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
&.shopByMobileOne {
|
||||
margin: 0 6px 0 0;
|
||||
}
|
||||
|
||||
.shopByMobileText {
|
||||
color: white !important;
|
||||
font-size: 25px !important;
|
||||
font-family: @baseFont !important; // LG Smart 폰트 사용
|
||||
font-weight: 400 !important; // Bold에서 Regular로 변경
|
||||
line-height: 35px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
// 포커스 상태 추가
|
||||
&:focus {
|
||||
background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경
|
||||
outline: 2px solid @PRIMARY_COLOR_RED !important;
|
||||
|
||||
.shopByMobileText {
|
||||
color: white !important; // 포커스시에도 텍스트는 흰색 유지
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.favoriteBtnWrapper {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
// 배경색과 라운드는 FavoriteBtn 내부에서 처리하므로 제거
|
||||
}
|
||||
|
||||
// 주문 전화 섹션 (callToOrderSection 참고)
|
||||
.callToOrderSection {
|
||||
align-self: stretch;
|
||||
height: 40px;
|
||||
padding: 17px 30px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px !important;
|
||||
.callToOrderText {
|
||||
color: #eaeaea;
|
||||
font-size: 25px;
|
||||
font-family: @baseFont; // LG Smart 폰트 사용
|
||||
font-weight: 400; // Bold에서 Regular로 변경
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
.phoneSection {
|
||||
padding: 0 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-right: 10px;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.phoneIconContainer {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.phoneIcon {
|
||||
width: 24.94px;
|
||||
height: 24.97px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
// 전화 아이콘 이미지 또는 CSS로 구현
|
||||
background-image: url("../../../../assets/images/icons/ic-gr-call-1.png");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.phoneNumber {
|
||||
color: #eaeaea;
|
||||
font-size: 25px;
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 액션 버튼들 (actionButtons 참고)
|
||||
.actionButtonsWrapper {
|
||||
align-self: stretch;
|
||||
padding-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
margin-bottom: 5px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 모든 버튼 기본 스타일 (PRODUCT DETAILS는 빨간색 아님!)
|
||||
.productDetailsButton,
|
||||
.userReviewsButton,
|
||||
.youMayLikeButton {
|
||||
align-self: stretch;
|
||||
height: 60px;
|
||||
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: #eaeaea;
|
||||
font-size: 25px;
|
||||
font-family: @baseFont; // LG Smart 폰트 사용
|
||||
font-weight: 400; // Bold에서 Regular로 변경
|
||||
line-height: 35px;
|
||||
|
||||
&:focus {
|
||||
background: #c72054; // 포커스시만 빨간색
|
||||
}
|
||||
}
|
||||
|
||||
.themeButton {
|
||||
align-self: stretch;
|
||||
height: 60px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
|
||||
&:focus {
|
||||
background: #c72054;
|
||||
}
|
||||
}
|
||||
|
||||
// QR 래퍼 (imageSection 참고 - 240px 고정)
|
||||
.qrWrapper {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProductOverview 컨테이너 스타일 수정 (자식 요소에 맞게 크기 조정)
|
||||
[class*="ProductOverview"] {
|
||||
padding: 0 0 5px;
|
||||
// 내부 div (productInfoWrapper)
|
||||
> div {
|
||||
align-self: stretch;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
> div:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
// LayoutSample 포커스 테스트용 스타일
|
||||
.layoutSample {
|
||||
width: 1124px;
|
||||
height: 300px;
|
||||
background-color: yellow;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: black;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
|
||||
&:focus {
|
||||
&::after {
|
||||
.focused(@boxShadow:22px, @borderRadius:8px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
// 1920px 화면 기준 전체 구조 (절대 위치로 정확히 배치)
|
||||
.detailArea {
|
||||
width: 1920px; // 명시적으로 화면 크기 설정
|
||||
height: 100%;
|
||||
padding: 0; // 모든 패딩 제거
|
||||
margin: 0; // 모든 마진 제거
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
position: relative; // 절대 위치 기준점
|
||||
|
||||
// Spotlight 좌우 이동을 위한 설정
|
||||
&:focus-within {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Left Margin Section - 60px
|
||||
.leftMarginSection {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// 2. Info Section - 645px
|
||||
.infoSection {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
width: 650px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
> div {
|
||||
height: 339px;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
|
||||
.contentSection {
|
||||
position: absolute;
|
||||
left: 705px; // 60px + 645px
|
||||
top: 0;
|
||||
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// 4. Scroll Section - 66px (삭제 - contentSection에 포함)
|
||||
.scrollSection {
|
||||
display: none; // 사용하지 않음
|
||||
}
|
||||
|
||||
// 왼쪽 영역 컨테이너 (infoSection 내부)
|
||||
.leftInfoContainer {
|
||||
width: 635px; // 실제 콘텐츠 영역
|
||||
margin-right: 10px; // 우측 10px 간격
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 왼쪽 영역 내부 래퍼
|
||||
.leftInfoWrapper {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
// gap 대신 margin 사용 (Chromium 68 호환성)
|
||||
> * {
|
||||
margin-bottom: 5px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 오른쪽 영역 컨테이너 (contentSection 내부)
|
||||
.rightContentContainer {
|
||||
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
||||
height: @globalHeight - 136px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
// 스크롤러 래퍼 (contentSection 내부)
|
||||
.scrollerWrapper {
|
||||
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바 (절대값)
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: visible; // hidden에서 visible로 변경
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
position: relative; // 자식 absolute 요소의 기준점
|
||||
|
||||
// 스크롤러 오버라이드 (1210px = 30px + content + 스크롤바)
|
||||
.scrollerOverride {
|
||||
width: 1210px; // 절대 크기 지정
|
||||
height: 100%;
|
||||
// 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용
|
||||
padding: 0 10px 0 30px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// 스크롤바 너비를 6px로 명확하게 설정
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent; // 트랙 배경은 투명하게
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #9c9c9c; // 스크롤바 색상
|
||||
border-radius: 3px; // 스크롤바 둥근 모서리
|
||||
}
|
||||
|
||||
// 스크롤바 thumb에 hover 효과 적용
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background: #c72054;
|
||||
}
|
||||
}
|
||||
|
||||
// 내부 콘텐츠는 별도 너비 계산 없이 100%를 사용
|
||||
> div {
|
||||
width: 100%; // 부모의 패딩을 제외한 나머지 공간(1114px)을 모두 사용
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
// 스크롤 콘텐츠 내부의 모든 섹션들 (패딩 제거 - scrollerOverride에서 처리함)
|
||||
#product-details-section {
|
||||
width: 1124px; // 부모 콘텐츠 영역 전체 사용
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
// ProductDetail.new 컴포넌트들
|
||||
> div[class*="rollingWrap"] {
|
||||
width: 100% !important; // 부모 영역 전체 사용
|
||||
max-width: none !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
#product-description-section,
|
||||
#user-reviews-section,
|
||||
#you-may-also-like-section {
|
||||
width: 100%; // 부모 콘텐츠 영역 전체 사용
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
|
||||
> * {
|
||||
max-width: 100% !important; // 부모 영역 전체 사용
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 이미지들이 컨테이너를 넘지 않도록
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 상품 상세 영역 (마진 포함 크기)
|
||||
.productDetail {
|
||||
width: 1124px; // 1114px + 10px
|
||||
max-width: 1124px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch; // 자식 요소들이 전체 너비 사용하도록
|
||||
}
|
||||
|
||||
// 스크롤 마커
|
||||
.scrollMarker {
|
||||
height: 1px;
|
||||
visibility: hidden;
|
||||
}
|
||||
} // .scrollerWrapper
|
||||
} // .rightContentContainer
|
||||
|
||||
// (중복 제거됨) .scrollerWrapper는 .rightContentContainer 하위로 중첩 이동
|
||||
|
||||
// (중복 제거됨) 최상위 스크롤러/섹션 정의는 .scrollerWrapper 중첩 내부로 이동
|
||||
|
||||
// ProductDetailCard 스타일 참고 - 크기/간격만 적용
|
||||
|
||||
// 헤더 컨텐츠 영역 (태그, 별점)
|
||||
.headerContent {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 모바일 쇼핑 섹션 (mobileSection 참고)
|
||||
.buttonContainer {
|
||||
align-self: stretch;
|
||||
padding-top: 19px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-right: 6px;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shopByMobileButton {
|
||||
flex: 1 1 0 !important;
|
||||
width: auto !important; // flex로 크기 조정
|
||||
height: 60px !important;
|
||||
background: rgba(68, 68, 68, 0.5) !important;
|
||||
border-radius: 6px !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
&.shopByMobileOne {
|
||||
margin: 0 6px 0 0;
|
||||
}
|
||||
|
||||
.shopByMobileText {
|
||||
color: white !important;
|
||||
@@ -308,7 +786,7 @@
|
||||
align-items: center;
|
||||
|
||||
.callToOrderText {
|
||||
color: #EAEAEA;
|
||||
color: #eaeaea;
|
||||
font-size: 25px;
|
||||
font-family: @baseFont; // LG Smart 폰트 사용
|
||||
font-weight: 400; // Bold에서 Regular로 변경
|
||||
@@ -322,7 +800,9 @@
|
||||
|
||||
> * {
|
||||
margin-right: 10px;
|
||||
&:last-child { margin-right: 0; }
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.phoneIconContainer {
|
||||
@@ -337,7 +817,6 @@
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: #EAEAEA;
|
||||
// 전화 아이콘 이미지 또는 CSS로 구현
|
||||
background-image: url("../../../../assets/images/icons/ic-gr-call-1.png");
|
||||
background-size: contain;
|
||||
@@ -347,9 +826,9 @@
|
||||
}
|
||||
|
||||
.phoneNumber {
|
||||
color: #EAEAEA;
|
||||
color: #eaeaea;
|
||||
font-size: 25px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
}
|
||||
@@ -365,7 +844,12 @@
|
||||
|
||||
> * {
|
||||
margin-bottom: 5px;
|
||||
&:last-child { margin-bottom: 0; }
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,14 +865,14 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: #EAEAEA;
|
||||
color: #eaeaea;
|
||||
font-size: 25px;
|
||||
font-family: @baseFont; // LG Smart 폰트 사용
|
||||
font-weight: 400; // Bold에서 Regular로 변경
|
||||
line-height: 35px;
|
||||
|
||||
&:focus {
|
||||
background: #C72054; // 포커스시만 빨간색
|
||||
background: #c72054; // 포커스시만 빨간색
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +884,7 @@
|
||||
margin-top: 10px;
|
||||
|
||||
&:focus {
|
||||
background: #C72054;
|
||||
background: #c72054;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,62 +898,31 @@
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
&:last-child { margin-bottom: 0; }
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProductOverview 컨테이너 스타일 수정 (자식 요소에 맞게 크기 조정)
|
||||
[class*="ProductOverview"] {
|
||||
align-self: stretch;
|
||||
padding: 0 0 5px; // ProductDetailCard mainContent와 동일한 패딩
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
padding: 0 0 5px;
|
||||
// 내부 div (productInfoWrapper)
|
||||
> div {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
> * {
|
||||
margin-right: 15px; // ProductDetailCard와 동일한 간격
|
||||
&:last-child { margin-right: 0; }
|
||||
}
|
||||
|
||||
// 가격 섹션 (flex로 남은 공간 차지)
|
||||
> div:first-child {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// 이미지 섹션 (QR 포함, 고정 크기)
|
||||
> div:last-child {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
&:last-child { margin-bottom: 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
> div:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
// LayoutSample 포커스 테스트용 스타일
|
||||
.layoutSample {
|
||||
width: 1124px;
|
||||
height: 35px;
|
||||
height: 300px;
|
||||
background-color: yellow;
|
||||
// border: 2px solid white;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
.title {
|
||||
.font(@fontFamily: @baseFont, @fontSize: 30px);
|
||||
font-weight: 700;
|
||||
margin: 90px 0 20px 0;
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,43 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import css from "./CustomerImages.module.less";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import THeader from "../../../../../components/THeader/THeader";
|
||||
import { $L } from "../../../../../utils/helperMethods";
|
||||
import classNames from "classnames";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import THeader from '../../../../../components/THeader/THeader';
|
||||
import { $L } from '../../../../../utils/helperMethods';
|
||||
import css from './CustomerImages.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{
|
||||
enterTo: "default-element",
|
||||
preserveld: true,
|
||||
leaveFor: {
|
||||
left: "spotlight-product-info-section-container"
|
||||
}
|
||||
left: "spotlight-product-info-section-container",
|
||||
},
|
||||
},
|
||||
"div"
|
||||
);
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
|
||||
export default function CustomerImages({ onImageClick, onViewMoreClick, imageData }) {
|
||||
export default function CustomerImages({
|
||||
onImageClick,
|
||||
onViewMoreClick,
|
||||
imageData,
|
||||
}) {
|
||||
// Props로 전달받은 imageData 사용 (useReviews Hook에서 추출된 이미지 데이터)
|
||||
const [selectedIndex, setSelectedIndex] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const IMAGES_PER_PAGE = 5;
|
||||
const [focusIdx, setFocusIdx] = useState(0);
|
||||
|
||||
// [CustomerImages] useReviews 이미지 데이터 수신 확인
|
||||
// useEffect(() => {
|
||||
@@ -58,25 +71,35 @@ export default function CustomerImages({ onImageClick, onViewMoreClick, imageDat
|
||||
};
|
||||
|
||||
// 키 이벤트 처리 (왼쪽 화살표, Enter 키)
|
||||
const handleKeyDown = useCallback((ev, index) => {
|
||||
if (ev.keyCode === 37) { // 왼쪽 화살표 키
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
console.log("[CustomerImages] Left arrow pressed, focusing product-details-button");
|
||||
Spotlight.focus("product-details-button");
|
||||
} else if (ev.keyCode === 13) { // Enter 키
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
console.log("[CustomerImages] Enter pressed on image:", index);
|
||||
handleReviewImageClick(index);
|
||||
}
|
||||
}, [handleReviewImageClick]);
|
||||
const handleKeyDown = useCallback(
|
||||
(ev, index) => {
|
||||
if (ev.keyCode === 37) {
|
||||
// 왼쪽 화살표 키
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
console.log(
|
||||
"[CustomerImages] Left arrow pressed, focusing product-details-button"
|
||||
);
|
||||
Spotlight.focus("product-details-button");
|
||||
} else if (ev.keyCode === 13) {
|
||||
// Enter 키
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
console.log("[CustomerImages] Enter pressed on image:", index);
|
||||
handleReviewImageClick(index);
|
||||
}
|
||||
},
|
||||
[handleReviewImageClick]
|
||||
);
|
||||
|
||||
// 이미지가 없을 때도 컴포넌트를 렌더링하되 내용은 표시하지 않음
|
||||
return (
|
||||
<>
|
||||
<Container className={css.container}>
|
||||
<THeader className={css.tHeader} title={$L("Customer Images")} />
|
||||
<THeader
|
||||
className={classNames(css.tHeader, css.customTHeader)}
|
||||
title={$L("Customer Images")}
|
||||
/>
|
||||
{imageData && imageData.length > 0 ? (
|
||||
<div className={css.wrapper}>
|
||||
{(() => {
|
||||
@@ -92,7 +115,7 @@ export default function CustomerImages({ onImageClick, onViewMoreClick, imageDat
|
||||
startIndex,
|
||||
endIndex,
|
||||
displayImagesCount: displayImages.length,
|
||||
hasMoreImages
|
||||
hasMoreImages,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -105,12 +128,18 @@ export default function CustomerImages({ onImageClick, onViewMoreClick, imageDat
|
||||
<SpottableComponent
|
||||
className={classNames(
|
||||
css.reviewCard,
|
||||
selectedIndex === actualIndex ? css.selectedReviewImage : null
|
||||
selectedIndex === actualIndex
|
||||
? css.selectedReviewImage
|
||||
: null,
|
||||
focusIdx === displayIndex ? css.focused : ""
|
||||
)}
|
||||
key={`review-image-${imgId}-${reviewId}`}
|
||||
onClick={() => handleReviewImageClick(actualIndex)}
|
||||
onKeyDown={(ev) => handleKeyDown(ev, actualIndex)}
|
||||
spotlightId={`customer-image-${actualIndex}`}
|
||||
onFocus={() =>
|
||||
displayIndex < 5 ? setFocusIdx(displayIndex) : ""
|
||||
}
|
||||
>
|
||||
<img
|
||||
className={css.reviewImg}
|
||||
@@ -124,13 +153,16 @@ export default function CustomerImages({ onImageClick, onViewMoreClick, imageDat
|
||||
}); */
|
||||
}}
|
||||
onError={(e) => {
|
||||
console.error(`[CustomerImages] Image load failed:`, {
|
||||
index: actualIndex,
|
||||
imgUrl,
|
||||
imgId,
|
||||
error: e.target.error
|
||||
});
|
||||
e.target.style.display = 'none';
|
||||
console.error(
|
||||
`[CustomerImages] Image load failed:`,
|
||||
{
|
||||
index: actualIndex,
|
||||
imgUrl,
|
||||
imgId,
|
||||
error: e.target.error,
|
||||
}
|
||||
);
|
||||
e.target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
</SpottableComponent>
|
||||
@@ -161,13 +193,17 @@ export default function CustomerImages({ onImageClick, onViewMoreClick, imageDat
|
||||
</div>
|
||||
) : (
|
||||
<div className={css.wrapper}>
|
||||
<div style={{
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
fontSize: '16px'
|
||||
}}>
|
||||
{imageData ? 'No customer images available' : 'Loading customer images...'}
|
||||
<div
|
||||
style={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{imageData
|
||||
? "No customer images available"
|
||||
: "Loading customer images..."}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.container {
|
||||
width: 1124px;
|
||||
height: 236px;
|
||||
height: 299px;
|
||||
max-width: 1124px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -14,7 +14,7 @@
|
||||
margin: 0 0 10px 0;
|
||||
|
||||
> div {
|
||||
padding: 0;
|
||||
padding: 0 0 0 20px;
|
||||
> span {
|
||||
.font(@fontFamily: @baseFont, @fontSize: 24px);
|
||||
font-weight: 400;
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 190px;
|
||||
height: 252px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
@@ -36,13 +36,25 @@
|
||||
overflow: visible;
|
||||
|
||||
.reviewCard {
|
||||
width: calc((100% - 75px) / 6); // 6개 슬롯(5이미지+1버튼), margin-right 15px * 5 = 75px
|
||||
height: 150px;
|
||||
// width: calc(
|
||||
// (100% - 75px) / 6
|
||||
// ); // 6개 슬롯(5이미지+1버튼), margin-right 15px * 5 = 75px
|
||||
// height: 150px;
|
||||
width: 157.3px;
|
||||
height: 192.5px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
margin-right: 15px;
|
||||
|
||||
&.focused {
|
||||
width: 211.75px;
|
||||
height: 211.75px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
//포커스시 확대
|
||||
// width: 211.75px;
|
||||
// height: 211.75px;
|
||||
&::after {
|
||||
.focused(@boxShadow:22px, @borderRadius:12px);
|
||||
}
|
||||
@@ -61,13 +73,18 @@
|
||||
}
|
||||
|
||||
.viewMoreButton {
|
||||
width: calc((100% - 75px) / 6); // reviewCard와 동일한 크기
|
||||
height: 150px;
|
||||
// width: calc((100% - 75px) / 6); // reviewCard와 동일한 크기
|
||||
// height: 150px;
|
||||
width: 157.3px;
|
||||
height: 192.5px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
// //포커스시 확대
|
||||
// width: 211.75px;
|
||||
// height: 211.75px;
|
||||
&::after {
|
||||
.focused(@boxShadow:22px, @borderRadius:12px);
|
||||
}
|
||||
@@ -77,7 +94,11 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 4px;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.80) 100%);
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.8) 0%,
|
||||
rgba(0, 0, 0, 0.8) 100%
|
||||
);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { useCallback } from "react";
|
||||
import classNames from "classnames";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import StarRating from "../../../components/StarRating";
|
||||
import { $L } from "../../../../../utils/helperMethods";
|
||||
import css from "./UserReviewDetail.module.less";
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { $L } from '../../../../../utils/helperMethods';
|
||||
import StarRating from '../../../components/StarRating';
|
||||
import css from './UserReviewDetail.module.less';
|
||||
|
||||
const SpottableButton = Spottable("div");
|
||||
|
||||
@@ -36,9 +39,15 @@ export default function UserReviewDetail({
|
||||
);
|
||||
}
|
||||
|
||||
const reviewImage = currentReview.reviewImageList && currentReview.reviewImageList[0];
|
||||
const reviewImage =
|
||||
currentReview.reviewImageList && currentReview.reviewImageList[0];
|
||||
const hasMultipleReviews = totalReviews > 1;
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
const [year, month, day] = dateStr.split("-");
|
||||
return `${year.slice(2)}.${month}.${day}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(css.container, className)}>
|
||||
{/* Left Arrow - 이전 리뷰가 있을 때만 표시 */}
|
||||
@@ -89,16 +98,14 @@ export default function UserReviewDetail({
|
||||
{/* Date */}
|
||||
{currentReview.rvwRgstDtt && (
|
||||
<div className={css.date}>
|
||||
{currentReview.rvwRgstDtt}
|
||||
{formatDate(currentReview.rvwRgstDtt)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Review Text */}
|
||||
{currentReview.rvwCtnt && (
|
||||
<div className={css.reviewText}>
|
||||
"{currentReview.rvwCtnt}"
|
||||
</div>
|
||||
<div className={css.reviewText}>"{currentReview.rvwCtnt}"</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import css from "./UserReviews.module.less";
|
||||
import UserReviewsScroller from "../../components/UserReviewsScroller/UserReviewsScroller";
|
||||
import useScrollTo from "../../../../hooks/useScrollTo";
|
||||
import THeaderDetail from "../../components/THeaderDetail";
|
||||
import { $L } from "../../../../utils/helperMethods";
|
||||
import { useMemo } from "react";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { toggleShowAllReviews } from "../../../../actions/productActions";
|
||||
import useReviews from "../../../../hooks/useReviews/useReviews";
|
||||
import StarRating from "../../components/StarRating";
|
||||
import CustomerImages from "./CustomerImages/CustomerImages";
|
||||
import UserReviewsPopup from "./UserReviewsPopup/UserReviewsPopup";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { toggleShowAllReviews } from '../../../../actions/productActions';
|
||||
import useReviews from '../../../../hooks/useReviews/useReviews';
|
||||
import useScrollTo from '../../../../hooks/useScrollTo';
|
||||
import { $L } from '../../../../utils/helperMethods';
|
||||
import StarRating from '../../components/StarRating';
|
||||
import THeaderDetail from '../../components/THeaderDetail';
|
||||
import UserReviewsScroller
|
||||
from '../../components/UserReviewsScroller/UserReviewsScroller';
|
||||
import CustomerImages from './CustomerImages/CustomerImages';
|
||||
import css from './UserReviews.module.less';
|
||||
import UserReviewsPopup from './UserReviewsPopup/UserReviewsPopup';
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
|
||||
@@ -22,10 +36,10 @@ const Container = SpotlightContainerDecorator(
|
||||
preserveld: true,
|
||||
leaveFor: {
|
||||
left: "spotlight-product-info-section-container",
|
||||
up: "view-all-reviews-button"
|
||||
up: "view-all-reviews-button",
|
||||
},
|
||||
restrict: "none",
|
||||
spotlightDirection: "vertical"
|
||||
spotlightDirection: "vertical",
|
||||
},
|
||||
"div"
|
||||
);
|
||||
@@ -43,7 +57,7 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
const actualReviewsData = reviewsData || {
|
||||
previewReviews: fallbackReviews.previewReviews,
|
||||
stats: fallbackReviews.stats,
|
||||
isLoading: fallbackReviews.isLoading
|
||||
isLoading: fallbackReviews.isLoading,
|
||||
};
|
||||
|
||||
// 팝업 상태 관리 - 모드와 선택된 이미지 인덱스 추가
|
||||
@@ -57,10 +71,17 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
useEffect(() => {
|
||||
console.log("[UserReviews] showAllReviews state changed:", {
|
||||
showAllReviews,
|
||||
reviewListLength: (actualReviewsData.previewReviews && actualReviewsData.previewReviews.length) || 0,
|
||||
willShowCount: showAllReviews ? ((actualReviewsData.previewReviews && actualReviewsData.previewReviews.length) || 0) : 5,
|
||||
reviewListLength:
|
||||
(actualReviewsData.previewReviews &&
|
||||
actualReviewsData.previewReviews.length) ||
|
||||
0,
|
||||
willShowCount: showAllReviews
|
||||
? (actualReviewsData.previewReviews &&
|
||||
actualReviewsData.previewReviews.length) ||
|
||||
0
|
||||
: 5,
|
||||
hasReviewsData: !!reviewsData,
|
||||
isFromProductAllSection: !!reviewsData
|
||||
isFromProductAllSection: !!reviewsData,
|
||||
});
|
||||
}, [showAllReviews, actualReviewsData.previewReviews, reviewsData]);
|
||||
|
||||
@@ -73,13 +94,16 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
setTimeout(() => {
|
||||
if (tScrollerRef.current) {
|
||||
// TScroller의 스크롤 영역을 강제로 업데이트
|
||||
if (typeof tScrollerRef.current.calculateMetrics === 'function') {
|
||||
if (typeof tScrollerRef.current.calculateMetrics === "function") {
|
||||
tScrollerRef.current.calculateMetrics();
|
||||
}
|
||||
|
||||
// 또는 scrollTo를 호출해서 스크롤 영역 업데이트
|
||||
if (typeof tScrollerRef.current.scrollTo === 'function') {
|
||||
tScrollerRef.current.scrollTo({ position: { y: 0 }, animate: false });
|
||||
if (typeof tScrollerRef.current.scrollTo === "function") {
|
||||
tScrollerRef.current.scrollTo({
|
||||
position: { y: 0 },
|
||||
animate: false,
|
||||
});
|
||||
}
|
||||
|
||||
// console.log("[UserReviews] TScroller scroll area updated");
|
||||
@@ -94,7 +118,7 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
const reviewDetailData = {
|
||||
totRvwCnt: actualReviewsData.stats.totalReviews,
|
||||
avgRvwScr: actualReviewsData.stats.averageRating,
|
||||
totRvwAvg: actualReviewsData.stats.averageRating
|
||||
totRvwAvg: actualReviewsData.stats.averageRating,
|
||||
};
|
||||
|
||||
// [UserReviews] 데이터 수신 확인 로그
|
||||
@@ -105,7 +129,9 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
averageRating: actualReviewsData.stats.averageRating,
|
||||
isLoading: actualReviewsData.isLoading,
|
||||
hasData: reviewListData && reviewListData.length > 0,
|
||||
dataSource: reviewsData ? 'ProductAllSection props' : 'useReviews fallback'
|
||||
dataSource: reviewsData
|
||||
? "ProductAllSection props"
|
||||
: "useReviews fallback",
|
||||
});
|
||||
}, [reviewListData, actualReviewsData, reviewsData]);
|
||||
|
||||
@@ -118,26 +144,32 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
};
|
||||
|
||||
// 리뷰 클릭으로 User Reviews 모드 팝업 열기
|
||||
const handleReviewClick = useCallback((reviewIndex) => {
|
||||
// 클릭한 리뷰 정보 (previewReviews에서)
|
||||
const clickedReview = reviewListData[reviewIndex];
|
||||
const handleReviewClick = useCallback(
|
||||
(reviewIndex) => {
|
||||
// 클릭한 리뷰 정보 (previewReviews에서)
|
||||
const clickedReview = reviewListData[reviewIndex];
|
||||
|
||||
// 전체 리뷰에서 클릭한 리뷰의 실제 인덱스 찾기
|
||||
const realIndex = fallbackReviews.allReviews.findIndex(
|
||||
review => review.rvwId === clickedReview.rvwId
|
||||
);
|
||||
// 전체 리뷰에서 클릭한 리뷰의 실제 인덱스 찾기
|
||||
const realIndex = fallbackReviews.allReviews.findIndex(
|
||||
(review) => review.rvwId === clickedReview.rvwId
|
||||
);
|
||||
|
||||
console.log("[UserReviews] Review clicked, opening popup in User Reviews mode:", {
|
||||
previewIndex: reviewIndex,
|
||||
realIndex,
|
||||
clickedReviewId: clickedReview.rvwId,
|
||||
totalReviews: fallbackReviews.allReviews.length
|
||||
});
|
||||
console.log(
|
||||
"[UserReviews] Review clicked, opening popup in User Reviews mode:",
|
||||
{
|
||||
previewIndex: reviewIndex,
|
||||
realIndex,
|
||||
clickedReviewId: clickedReview.rvwId,
|
||||
totalReviews: fallbackReviews.allReviews.length,
|
||||
}
|
||||
);
|
||||
|
||||
setSelectedImageIndex(realIndex >= 0 ? realIndex : reviewIndex);
|
||||
setPopupMode("user-reviews");
|
||||
setIsPopupOpen(true);
|
||||
}, [reviewListData, fallbackReviews.allReviews]);
|
||||
setSelectedImageIndex(realIndex >= 0 ? realIndex : reviewIndex);
|
||||
setPopupMode("user-reviews");
|
||||
setIsPopupOpen(true);
|
||||
},
|
||||
[reviewListData, fallbackReviews.allReviews]
|
||||
);
|
||||
|
||||
// 팝업 관련 핸들러들
|
||||
// +View More 버튼으로 Customer Images 모드 팝업 열기
|
||||
@@ -149,7 +181,10 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
|
||||
// 이미지 클릭으로 All-Images 모드 팝업 열기
|
||||
const handleOpenAllImagesPopup = useCallback((imageIndex = 0) => {
|
||||
console.log("[UserReviews] Opening popup in All-Images mode, image index:", imageIndex);
|
||||
console.log(
|
||||
"[UserReviews] Opening popup in All-Images mode, image index:",
|
||||
imageIndex
|
||||
);
|
||||
setSelectedImageIndex(imageIndex);
|
||||
setPopupMode("all-images");
|
||||
setIsPopupOpen(true);
|
||||
@@ -164,7 +199,10 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
|
||||
// 팝업 모드 변경 핸들러
|
||||
const handleModeChange = useCallback((newMode, imageIndex = 0) => {
|
||||
console.log("[UserReviews] Mode change requested:", { newMode, imageIndex });
|
||||
console.log("[UserReviews] Mode change requested:", {
|
||||
newMode,
|
||||
imageIndex,
|
||||
});
|
||||
setPopupMode(newMode);
|
||||
if (newMode === "all-images" || newMode === "user-reviews") {
|
||||
setSelectedImageIndex(imageIndex);
|
||||
@@ -173,9 +211,6 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
|
||||
// handleImageClick 제거 - 더 이상 필요없음
|
||||
|
||||
|
||||
|
||||
|
||||
// customerImages 로직 제거 - useReviews의 extractImagesFromReviews 사용
|
||||
|
||||
return (
|
||||
@@ -190,13 +225,15 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
verticalScrollbar="auto"
|
||||
cbScrollTo={getScrollTo}
|
||||
forceUpdate={showAllReviews}
|
||||
key={showAllReviews ? `all-${(reviewListData && reviewListData.length) || 0}` : 'limited-5'}
|
||||
key={
|
||||
showAllReviews
|
||||
? `all-${(reviewListData && reviewListData.length) || 0}`
|
||||
: "limited-5"
|
||||
}
|
||||
>
|
||||
<THeaderDetail
|
||||
title={$L(
|
||||
`USER REVIEWS (${reviewTotalCount})`
|
||||
)}
|
||||
className={css.tHeader}
|
||||
title={$L(`USER REVIEWS (${reviewTotalCount})`)}
|
||||
className={classNames(css.tHeader, css.tHeaderDetail)}
|
||||
>
|
||||
{reviewDetailData && reviewDetailData.totRvwAvg && (
|
||||
<StarRating
|
||||
@@ -219,31 +256,48 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
</div>
|
||||
{reviewListData &&
|
||||
(() => {
|
||||
const reviewsToShow = showAllReviews ? reviewListData : reviewListData.slice(0, 5);
|
||||
const reviewsToShow = showAllReviews
|
||||
? reviewListData
|
||||
: reviewListData.slice(0, 5);
|
||||
console.log("[UserReviews] Reviews to render:", {
|
||||
showAllReviews,
|
||||
totalReviews: reviewListData.length,
|
||||
reviewsToShowCount: reviewsToShow.length,
|
||||
isShowingAll: showAllReviews,
|
||||
reviewListData: reviewListData
|
||||
reviewListData: reviewListData,
|
||||
});
|
||||
|
||||
// 실제 렌더링될 각 리뷰 로그
|
||||
reviewsToShow.forEach((review, index) => {
|
||||
console.log(`[UserReviews] Review ${index + 1}/${reviewsToShow.length}:`, {
|
||||
rvwId: review.rvwId,
|
||||
rvwCtnt: (review.rvwCtnt && review.rvwCtnt.substring(0, 50)) + "...",
|
||||
rvwRtng: review.rvwRtng,
|
||||
hasImages: (review.reviewImageList && review.reviewImageList.length) || 0,
|
||||
rvwRgstDtt: review.rvwRgstDtt,
|
||||
fullReview: review
|
||||
});
|
||||
console.log(
|
||||
`[UserReviews] Review ${index + 1}/${reviewsToShow.length}:`,
|
||||
{
|
||||
rvwId: review.rvwId,
|
||||
rvwCtnt:
|
||||
(review.rvwCtnt && review.rvwCtnt.substring(0, 50)) +
|
||||
"...",
|
||||
rvwRtng: review.rvwRtng,
|
||||
hasImages:
|
||||
(review.reviewImageList &&
|
||||
review.reviewImageList.length) ||
|
||||
0,
|
||||
rvwRgstDtt: review.rvwRgstDtt,
|
||||
fullReview: review,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return reviewsToShow;
|
||||
})().map((review, index, array) => {
|
||||
const { reviewImageList, rvwRtng, rvwRgstDtt, rvwCtnt, rvwId, wrtrNknm, rvwWrtrId } =
|
||||
review;
|
||||
const {
|
||||
reviewImageList,
|
||||
rvwRtng,
|
||||
rvwRgstDtt,
|
||||
rvwCtnt,
|
||||
rvwId,
|
||||
wrtrNknm,
|
||||
rvwWrtrId,
|
||||
} = review;
|
||||
const isLastReview = index === array.length - 1;
|
||||
/* console.log(`[UserReviews] Rendering review ${index}:`, {
|
||||
rvwId,
|
||||
@@ -258,7 +312,11 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
aria-label={`user-reviews-:${rvwId}`}
|
||||
className={css.reviewContentContainer}
|
||||
onClick={() => handleReviewClick(index)}
|
||||
spotlightId={isLastReview ? 'user-review-at-last' : `user-review-${index}`}
|
||||
spotlightId={
|
||||
isLastReview
|
||||
? "user-review-at-last"
|
||||
: `user-review-${index}`
|
||||
}
|
||||
>
|
||||
{reviewImageList && reviewImageList.length > 0 && (
|
||||
<img
|
||||
@@ -285,14 +343,11 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{rvwCtnt && (
|
||||
<div className={css.reviewText}>{rvwCtnt}</div>
|
||||
)}
|
||||
{rvwCtnt && <div className={css.reviewText}>{rvwCtnt}</div>}
|
||||
</div>
|
||||
</SpottableComponent>
|
||||
);
|
||||
})}
|
||||
|
||||
</div>
|
||||
</UserReviewsScroller>
|
||||
|
||||
|
||||
@@ -28,14 +28,26 @@
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.tHeaderDetail {
|
||||
height: 56px;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> div {
|
||||
.size(@w:100%,@h:100%);
|
||||
padding: 0;
|
||||
&:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
> &.title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.averageOverallRating {
|
||||
.size(@w: 176px,@h:30px);
|
||||
width: 176px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
@@ -54,7 +66,7 @@
|
||||
.reviewItem {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
margin-top: 10px;
|
||||
.showReviewsText {
|
||||
.size(@w:100%, @h:36px);
|
||||
.font(@fontFamily: @baseFont, @fontSize: 24px);
|
||||
@@ -159,13 +171,13 @@
|
||||
padding: 20px 30px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 6px;
|
||||
border: 1px solid #EEEEEE;
|
||||
border: 1px solid #eeeeee;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.viewAllReviewsText {
|
||||
color: #EAEAEA;
|
||||
color: #eaeaea;
|
||||
font-size: 24px;
|
||||
font-family: @baseFont;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import React, { useCallback, useState, useEffect } from "react";
|
||||
import classNames from "classnames";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import TNewPopUp from "../../../../../components/TPopUp/TNewPopUp";
|
||||
import TButton from "../../../../../components/TButton/TButton";
|
||||
import { $L } from "../../../../../utils/helperMethods";
|
||||
import UserReviewDetail from "../UserReviewDetail/UserReviewDetail";
|
||||
import ImageSkeleton from "./ImageSkeleton/ImageSkeleton";
|
||||
import css from "./UserReviewsPopup.module.less";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import TButton from '../../../../../components/TButton/TButton';
|
||||
import TNewPopUp from '../../../../../components/TPopUp/TNewPopUp';
|
||||
import useScrollTo from '../../../../../hooks/useScrollTo';
|
||||
import { $L } from '../../../../../utils/helperMethods';
|
||||
import TScrollerDetail from '../../../components/TScroller/TScrollerDetail';
|
||||
import UserReviewDetail from '../UserReviewDetail/UserReviewDetail';
|
||||
import ImageSkeleton from './ImageSkeleton/ImageSkeleton';
|
||||
import css from './UserReviewsPopup.module.less';
|
||||
|
||||
const SpottableImage = Spottable("div");
|
||||
|
||||
@@ -24,7 +35,7 @@ const FooterContainer = SpotlightContainerDecorator(
|
||||
{
|
||||
enterTo: "default-element",
|
||||
preserveId: true,
|
||||
defaultElement: "close-button"
|
||||
defaultElement: "close-button",
|
||||
},
|
||||
"div"
|
||||
);
|
||||
@@ -40,6 +51,9 @@ export default function UserReviewsPopup({
|
||||
onModeChange, // 모드 변경 콜백 함수
|
||||
className,
|
||||
}) {
|
||||
const scrollContainerRef = useRef(null);
|
||||
const { getScrollTo } = useScrollTo();
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
@@ -47,12 +61,18 @@ export default function UserReviewsPopup({
|
||||
}, [onClose]);
|
||||
|
||||
// Customer Images 모드에서 이미지 클릭 시 All Images 모드로 변경
|
||||
const handleCustomerImageClick = useCallback((index) => {
|
||||
console.log("[UserReviewsPopup] Customer image clicked, switching to All Images mode, index:", index);
|
||||
if (onModeChange) {
|
||||
onModeChange("all-images", index);
|
||||
}
|
||||
}, [onModeChange]);
|
||||
const handleCustomerImageClick = useCallback(
|
||||
(index) => {
|
||||
console.log(
|
||||
"[UserReviewsPopup] Customer image clicked, switching to All Images mode, index:",
|
||||
index
|
||||
);
|
||||
if (onModeChange) {
|
||||
onModeChange("all-images", index);
|
||||
}
|
||||
},
|
||||
[onModeChange]
|
||||
);
|
||||
|
||||
// 모드별 헤더 정보
|
||||
const getHeaderInfo = useCallback((mode) => {
|
||||
@@ -61,20 +81,20 @@ export default function UserReviewsPopup({
|
||||
return {
|
||||
title: $L("All images"),
|
||||
hasIcon: true,
|
||||
iconType: "all-images"
|
||||
iconType: "all-images",
|
||||
};
|
||||
case "user-reviews":
|
||||
return {
|
||||
title: $L("User Reviews"),
|
||||
hasIcon: true,
|
||||
iconType: "user-reviews"
|
||||
iconType: "user-reviews",
|
||||
};
|
||||
case "customer-images":
|
||||
default:
|
||||
return {
|
||||
title: $L("Customer Images"),
|
||||
hasIcon: false,
|
||||
iconType: null
|
||||
iconType: null,
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
@@ -88,8 +108,8 @@ export default function UserReviewsPopup({
|
||||
useEffect(() => {
|
||||
if (mode === "all-images" && images && images[selectedImageIndex]) {
|
||||
const selectedImage = images[selectedImageIndex];
|
||||
const reviewIndex = reviewsWithImages.findIndex(review =>
|
||||
review.rvwId === selectedImage.reviewId
|
||||
const reviewIndex = reviewsWithImages.findIndex(
|
||||
(review) => review.rvwId === selectedImage.reviewId
|
||||
);
|
||||
if (reviewIndex !== -1) {
|
||||
setCurrentReviewIndex(reviewIndex);
|
||||
@@ -108,9 +128,10 @@ export default function UserReviewsPopup({
|
||||
}, [currentReviewIndex]);
|
||||
|
||||
const handleNextReview = useCallback(() => {
|
||||
const maxIndex = mode === "user-reviews"
|
||||
? allReviews.length - 1
|
||||
: reviewsWithImages.length - 1;
|
||||
const maxIndex =
|
||||
mode === "user-reviews"
|
||||
? allReviews.length - 1
|
||||
: reviewsWithImages.length - 1;
|
||||
|
||||
if (currentReviewIndex < maxIndex) {
|
||||
setCurrentReviewIndex(currentReviewIndex + 1);
|
||||
@@ -118,32 +139,28 @@ export default function UserReviewsPopup({
|
||||
}, [currentReviewIndex, mode, allReviews.length, reviewsWithImages.length]);
|
||||
|
||||
// All Images 아이콘 컴포넌트 (PNG 파일 사용)
|
||||
const AllImagesIcon = () => (
|
||||
<div className={css.headerIcon} />
|
||||
);
|
||||
const AllImagesIcon = () => <div className={css.headerIcon} />;
|
||||
|
||||
// User Reviews 아이콘 컴포넌트 (추후 구현)
|
||||
const UserReviewsIcon = () => (
|
||||
<div className={css.headerIcon}>
|
||||
{/* User Reviews 아이콘 내용 */}
|
||||
</div>
|
||||
<div className={css.headerIcon}>{/* User Reviews 아이콘 내용 */}</div>
|
||||
);
|
||||
|
||||
// 이미지 로딩 상태 관리
|
||||
const [imageLoadStates, setImageLoadStates] = useState({});
|
||||
|
||||
const handleImageLoad = useCallback((index) => {
|
||||
setImageLoadStates(prev => ({
|
||||
setImageLoadStates((prev) => ({
|
||||
...prev,
|
||||
[index]: true
|
||||
[index]: true,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleImageError = useCallback((index) => {
|
||||
console.error(`[UserReviewsPopup] Image load failed for index: ${index}`);
|
||||
setImageLoadStates(prev => ({
|
||||
setImageLoadStates((prev) => ({
|
||||
...prev,
|
||||
[index]: false
|
||||
[index]: false,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
@@ -171,61 +188,74 @@ export default function UserReviewsPopup({
|
||||
{headerInfo.iconType === "user-reviews" && <UserReviewsIcon />}
|
||||
</>
|
||||
)}
|
||||
<div className={css.headerTitle}>
|
||||
{headerInfo.title}
|
||||
</div>
|
||||
<div className={css.headerTitle}>{headerInfo.title}</div>
|
||||
</div>
|
||||
|
||||
{/* Content - 모드별 내용 */}
|
||||
<ContentContainer
|
||||
className={css.content}
|
||||
defaultElement={
|
||||
mode === "all-images" ? "review-detail-prev" :
|
||||
mode === "user-reviews" ? "user-review-detail-prev" :
|
||||
"user-review-image-0"
|
||||
mode === "all-images"
|
||||
? "review-detail-prev"
|
||||
: mode === "user-reviews"
|
||||
? "user-review-detail-prev"
|
||||
: "user-review-image-0"
|
||||
}
|
||||
>
|
||||
{mode === "customer-images" && (
|
||||
<div className={css.imageGrid}>
|
||||
{displayImages.map((image, index) => {
|
||||
const isLoaded = imageLoadStates[index] === true;
|
||||
const isFailed = imageLoadStates[index] === false;
|
||||
<TScrollerDetail
|
||||
ref={scrollContainerRef}
|
||||
verticalScrollbar="visible"
|
||||
className={css.scrollerOverride}
|
||||
cbScrollTo={getScrollTo}
|
||||
spotlightDisabled={false}
|
||||
spotlightRestrict="none"
|
||||
>
|
||||
<div className={css.imageGrid}>
|
||||
{displayImages.map((image, index) => {
|
||||
const isLoaded = imageLoadStates[index] === true;
|
||||
const isFailed = imageLoadStates[index] === false;
|
||||
|
||||
return (
|
||||
<SpottableImage
|
||||
key={`user-review-image-${index}`}
|
||||
spotlightId={`user-review-image-${index}`}
|
||||
className={css.imageItem}
|
||||
onClick={() => handleCustomerImageClick(index)}
|
||||
>
|
||||
{/* Skeleton UI - 이미지 로딩 중일 때 표시 */}
|
||||
{!isLoaded && !isFailed && (
|
||||
<ImageSkeleton className={css.image} />
|
||||
)}
|
||||
return (
|
||||
<SpottableImage
|
||||
key={`user-review-image-${index}`}
|
||||
spotlightId={`user-review-image-${index}`}
|
||||
className={css.imageItem}
|
||||
onClick={() => handleCustomerImageClick(index)}
|
||||
>
|
||||
<div>
|
||||
{/* Skeleton UI - 이미지 로딩 중일 때 표시 */}
|
||||
{!isLoaded && !isFailed && (
|
||||
<ImageSkeleton className={css.image} />
|
||||
)}
|
||||
|
||||
{/* 실제 이미지 */}
|
||||
<img
|
||||
src={image.imgUrl || image}
|
||||
alt={`Customer review ${index + 1}`}
|
||||
className={css.image}
|
||||
style={{
|
||||
opacity: isLoaded ? 1 : 0,
|
||||
transition: 'opacity 0.3s ease-in-out'
|
||||
}}
|
||||
onLoad={() => handleImageLoad(index)}
|
||||
onError={() => handleImageError(index)}
|
||||
/>
|
||||
{/* 실제 이미지 */}
|
||||
<img
|
||||
src={image.imgUrl || image}
|
||||
alt={`Customer review ${index + 1}`}
|
||||
className={css.image}
|
||||
style={{
|
||||
opacity: isLoaded ? 1 : 0,
|
||||
transition: "opacity 0.3s ease-in-out",
|
||||
}}
|
||||
onLoad={() => handleImageLoad(index)}
|
||||
onError={() => handleImageError(index)}
|
||||
/>
|
||||
|
||||
{/* 이미지 로드 실패 시 표시할 플레이스홀더 */}
|
||||
{isFailed && (
|
||||
<div className={css.imagePlaceholder}>
|
||||
<div className={css.placeholderText}>이미지 없음</div>
|
||||
{/* 이미지 로드 실패 시 표시할 플레이스홀더 */}
|
||||
{isFailed && (
|
||||
<div className={css.imagePlaceholder}>
|
||||
<div className={css.placeholderText}>
|
||||
이미지 없음
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SpottableImage>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</SpottableImage>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TScrollerDetail>
|
||||
)}
|
||||
|
||||
{mode === "all-images" && (
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
.header {
|
||||
align-self: stretch;
|
||||
padding: 30px;
|
||||
background: #E7EBEF;
|
||||
background: #e7ebef;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
@@ -55,117 +55,171 @@
|
||||
align-items: center; // 중앙 정렬
|
||||
position: relative;
|
||||
|
||||
.imageGrid {
|
||||
width: 100%;
|
||||
.scrollerOverride {
|
||||
width: 1060px; // 절대 크기 지정
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center; // 중앙 정렬로 균등 배치
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
overflow-y: scroll; // 스크롤바 항상 표시
|
||||
overflow-x: hidden;
|
||||
padding: 30px 40px; // 좌우 패딩 증가
|
||||
// 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용
|
||||
padding: 30px 20px 30px 20px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정
|
||||
.imageItem {
|
||||
width: 210px; // 크기 약간 증가
|
||||
height: 190px; // 비율 맞춤
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin-right: 35px; // 마진 증가로 균등 분배
|
||||
margin-bottom: 30px; // 세로 마진도 증가
|
||||
|
||||
// 4개씩 배치하므로 4번째마다 margin-right 제거
|
||||
&:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
&::after {
|
||||
.focused(@boxShadow: 0px, @borderRadius: 12px);
|
||||
// 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED)
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 이미지 로드 실패 시 플레이스홀더
|
||||
.imagePlaceholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.placeholderText {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
font-family: @baseFont;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// selectedImage 스타일 제거 - 오직 포커스만 사용
|
||||
// 스크롤바 너비를 6px로 명확하게 설정
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
// View More 아이템
|
||||
.viewMoreItem {
|
||||
width: 210px; // 크기 증가
|
||||
height: 190px; // 비율 맞춤
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
margin-right: 35px; // 마진 증가
|
||||
margin-bottom: 30px; // 세로 마진 증가
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent; // 트랙 배경은 투명하게
|
||||
}
|
||||
|
||||
&:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #9c9c9c; // 스크롤바 색상
|
||||
border-radius: 3px; // 스크롤바 둥근 모서리
|
||||
}
|
||||
|
||||
// 스크롤바 thumb에 hover 효과 적용
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background: #c72054;
|
||||
}
|
||||
// gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정
|
||||
.imageGrid {
|
||||
width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
// padding: 30px 40px; // 좌우 패딩 증가
|
||||
// padding: 30px 0px 30px 15px; // 좌우 패딩 증가
|
||||
.imageItem {
|
||||
//스타일 변경
|
||||
// width: 210px; // 크기 약간 증가
|
||||
// height: 190px; // 비율 맞춤
|
||||
|
||||
width: 226px; // 크기 약간 증가
|
||||
height: 218px; // 비율 맞춤
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
//스타일 변경
|
||||
// margin-right: 35px; // 마진 증가로 균등 분배
|
||||
// margin-bottom: 30px; // 세로 마진도 증가
|
||||
margin-right: 20px; // 마진 증가로 균등 분배
|
||||
margin-top: 20px; // 세로 마진도 증가
|
||||
|
||||
// 4개씩 배치하므로 4번째마다 margin-right 제거
|
||||
&:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
//확대 이미지
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
margin-right: 6px;
|
||||
margin-top: -2px;
|
||||
// margin-top: -3px;
|
||||
outline: none;
|
||||
&::after {
|
||||
.focused(@boxShadow: 0px, @borderRadius: 12px);
|
||||
// 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED)
|
||||
}
|
||||
&:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
margin-left: -14px;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 이미지 로드 실패 시 플레이스홀더
|
||||
.imagePlaceholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.placeholderText {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
font-family: @baseFont;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// selectedImage 스타일 제거 - 오직 포커스만 사용
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// View More 아이템
|
||||
.viewMoreItem {
|
||||
width: 210px; // 크기 증가
|
||||
height: 190px; // 비율 맞춤
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
position: relative;
|
||||
margin-right: 35px; // 마진 증가
|
||||
margin-bottom: 30px; // 세로 마진 증가
|
||||
|
||||
.viewMoreOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.80) 100%);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
&:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.viewMoreText {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-family: @baseFont;
|
||||
font-weight: 700;
|
||||
line-height: 31px;
|
||||
word-wrap: break-word;
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.viewMoreOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.8) 0%,
|
||||
rgba(0, 0, 0, 0.8) 100%
|
||||
);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.viewMoreText {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-family: @baseFont;
|
||||
font-weight: 700;
|
||||
line-height: 31px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +260,7 @@
|
||||
.closeButton {
|
||||
width: 300px !important;
|
||||
height: 78px !important;
|
||||
background: #7A808D !important;
|
||||
background: #7a808d !important;
|
||||
border-radius: 12px !important;
|
||||
border: none !important;
|
||||
display: flex !important;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useMemo } from "react";
|
||||
import css from "./QRCode.module.less";
|
||||
import { getQRCodeUrl } from "../../../../utils/helperMethods";
|
||||
import TQRCode from "../../../../components/TQRCode/TQRCode";
|
||||
import { useSelector } from "react-redux";
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import TQRCode from '../../../../components/TQRCode/TQRCode';
|
||||
import { getQRCodeUrl } from '../../../../utils/helperMethods';
|
||||
import css from './QRCode.module.less';
|
||||
|
||||
export default function QRCode({
|
||||
productType,
|
||||
@@ -59,7 +61,7 @@ export default function QRCode({
|
||||
{kind === "detail" ? (
|
||||
<TQRCode text={qrCodeUrl} width="240" height="240" />
|
||||
) : (
|
||||
""
|
||||
qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />
|
||||
)}
|
||||
{/* todo : 시나리오,UI 릴리즈 후 */}
|
||||
{/* <div className={css.tooltip}>
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
.size(@w:100%,@h:100%);
|
||||
|
||||
// .size(@w:100%,@h:100%);
|
||||
.size(@w:100%,@h:334px);
|
||||
.productInfoWrapper {
|
||||
.flex(@justifyCenter:flex-start,@alignCenter:flex-start);
|
||||
margin: 54px 0 10px 0;
|
||||
// margin: 54px 0 10px 0;
|
||||
margin: 20px 0 10px 0;
|
||||
// 고정 높이로 인해 QR 영역과 하단 버튼 영역 사이에 과도한 여백이 생김
|
||||
// 콘텐츠 높이에 맞춰 자동으로 계산되도록 변경하여 불필요한 간격 제거
|
||||
.size(@w:100%,@h:auto);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import usePriceInfo from "../../../../../hooks/usePriceInfo";
|
||||
import { $L } from "../../../../../utils/helperMethods";
|
||||
import css from "./BuyNowPriceDisplay.module.less";
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import usePriceInfo from '../../../../../hooks/usePriceInfo';
|
||||
import { $L } from '../../../../../utils/helperMethods';
|
||||
import css from './BuyNowPriceDisplay.module.less';
|
||||
|
||||
export default function BuyNowPriceDisplay({
|
||||
priceData,
|
||||
@@ -41,16 +45,26 @@ export default function BuyNowPriceDisplay({
|
||||
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
|
||||
<div className={css.rateTag}>{discountRate}</div>
|
||||
)}
|
||||
<span className={css.price}>
|
||||
{isDiscountedPriceEmpty ? offerInfo : discountedPrice}
|
||||
</span>
|
||||
{isDiscounted && (
|
||||
<span className={css.discountedPrc}>
|
||||
{originalPrice && isOriginalPriceEmpty
|
||||
? offerInfo
|
||||
: originalPrice}
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
display: "inline-flex",
|
||||
height: "60px",
|
||||
lineHeight: "60px",
|
||||
}}
|
||||
>
|
||||
<span className={css.price}>
|
||||
{isDiscountedPriceEmpty ? offerInfo : discountedPrice}
|
||||
</span>
|
||||
)}
|
||||
{isDiscounted && (
|
||||
<span className={css.discountedPrc}>
|
||||
{originalPrice && isOriginalPriceEmpty
|
||||
? offerInfo
|
||||
: originalPrice}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* 할부 */}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
@import "../../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../../style/utils.module.less";
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
|
||||
.partnerName {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
color: @COLOR_WHITE;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.flex(@alignCenter:flex-start,@direction:column);
|
||||
.topLayer {
|
||||
height: 42px;
|
||||
flex-wrap: wrap;
|
||||
// margin-bottom: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.rateTag {
|
||||
background: linear-gradient(309.43deg, #ef775b 23.84%, #c70850 100%);
|
||||
width: 70px;
|
||||
height: 42px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
color: @COLOR_WHITE;
|
||||
.font(@fontFamily: @baseFont, @fontSize: 24px);
|
||||
font-weight: 700;
|
||||
.flex();
|
||||
}
|
||||
.name {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
color: @COLOR_WHITE;
|
||||
width: 100%;
|
||||
}
|
||||
.btmLayer {
|
||||
//margin-top:14px;
|
||||
margin: 10px 0;
|
||||
// display: flex;rateTag
|
||||
align-items: center;
|
||||
}
|
||||
.price {
|
||||
font-weight: bold;
|
||||
font-size: 60px;
|
||||
color: @COLOR_WHITE;
|
||||
margin-right: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
.offerInfo {
|
||||
.elip(4);
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
color: #808080;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.discountedPrc {
|
||||
font-size: 24px;
|
||||
color: @COLOR_GRAY03;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
//rewd Layer
|
||||
.rewdTopLayer {
|
||||
width: 500px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid @COLOR_GRAY02;
|
||||
|
||||
> span {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
color: @COLOR_GRAY03;
|
||||
}
|
||||
.partnerPrc {
|
||||
text-decoration: line-through;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.rewdBtmLayer {
|
||||
padding-top: 24px;
|
||||
.flex(@direction:column,@alignCenter:flex-start);
|
||||
.rewdNm {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
color: @COLOR_BLACK;
|
||||
}
|
||||
|
||||
.btmPrc {
|
||||
margin-top: 17px;
|
||||
.flex();
|
||||
.rewdPrc {
|
||||
font-size: 24px;
|
||||
color: #808080;
|
||||
margin-right: 10px;
|
||||
padding-top: 14px;
|
||||
}
|
||||
}
|
||||
.rewdRate {
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
color: @PRIMARY_COLOR_RED;
|
||||
margin-top: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
|
||||
return (
|
||||
<>
|
||||
{productType && productInfo && (
|
||||
<div>
|
||||
/* <div> */
|
||||
<div style={{ margin: "0 10px 0 0", width: "380px" }}>
|
||||
{/* shop by mobile (구매불가) 상품 price render */}
|
||||
{(productType === "shopByMobile" || isThemeShopByMobile) && (
|
||||
<ShopByMobilePriceDisplay
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
}
|
||||
.flex(@alignCenter:flex-start,@direction:column);
|
||||
.topLayer {
|
||||
margin-bottom: 20px;
|
||||
height: 42px;
|
||||
flex-wrap: wrap;
|
||||
// margin-bottom: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.rateTag {
|
||||
background: linear-gradient(309.43deg, #ef775b 23.84%, #c70850 100%);
|
||||
@@ -34,14 +35,15 @@
|
||||
width: 100%;
|
||||
}
|
||||
.btmLayer {
|
||||
margin-top: 14px;
|
||||
//margin-top:14px;
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.price {
|
||||
font-weight: bold;
|
||||
font-size: 60px;
|
||||
color: @PRIMARY_COLOR_RED;
|
||||
color: @COLOR_WHITE;
|
||||
margin-right: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user