[251018] fix: EnergyLabel

🕐 커밋 시간: 2025. 10. 18. 23:31:25

📊 변경 통계:
  • 총 파일: 7개
  • 추가: +387줄
  • 삭제: -93줄

📁 추가된 파일:
  + com.twin.app.shoptime/assets/mock/EnergyLabelSample.pdf

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/convertActions.js
  ~ com.twin.app.shoptime/src/api/TAxios.js
  ~ com.twin.app.shoptime/src/components/TItemCard/TItemCard.module.less
  ~ com.twin.app.shoptime/src/components/TItemCard/TItemCard.new.jsx
  ~ com.twin.app.shoptime/src/reducers/convertReducer.js

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/components/TItemCard/TItemCard.new.jsx (javascript):
     Added: hashCode()

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • API 서비스 레이어 개선
  • UI 컴포넌트 아키텍처 개선
This commit is contained in:
2025-10-18 23:31:29 +09:00
parent 5bddacb3af
commit 0781bb39b2
7 changed files with 2272 additions and 522 deletions

File diff suppressed because one or more lines are too long

View File

@@ -2,220 +2,220 @@
export const types = { export const types = {
// panel actions // panel actions
PUSH_PANEL: "PUSH_PANEL", PUSH_PANEL: 'PUSH_PANEL',
POP_PANEL: "POP_PANEL", POP_PANEL: 'POP_PANEL',
UPDATE_PANEL: "UPDATE_PANEL", UPDATE_PANEL: 'UPDATE_PANEL',
RESET_PANELS: "RESET_PANELS", RESET_PANELS: 'RESET_PANELS',
// device actions // device actions
GET_AUTHENTICATION_CODE: "GET_AUTHENTICATION_CODE", GET_AUTHENTICATION_CODE: 'GET_AUTHENTICATION_CODE',
REGISTER_DEVICE: "REGISTER_DEVICE", REGISTER_DEVICE: 'REGISTER_DEVICE',
REGISTER_DEVICE_INFO: "REGISTER_DEVICE_INFO", REGISTER_DEVICE_INFO: 'REGISTER_DEVICE_INFO',
GET_DEVICE_INFO: "GET_DEVICE_INFO", GET_DEVICE_INFO: 'GET_DEVICE_INFO',
CLEAR_REGISTER_DEVICE_INFO: "CLEAR_REGISTER_DEVICE_INFO", CLEAR_REGISTER_DEVICE_INFO: 'CLEAR_REGISTER_DEVICE_INFO',
REGISTER_DEVICE_RESET: "REGISTER_DEVICE_RESET", REGISTER_DEVICE_RESET: 'REGISTER_DEVICE_RESET',
// common actions // common actions
GET_HTTP_HEADER: "GET_HTTP_HEADER", GET_HTTP_HEADER: 'GET_HTTP_HEADER',
CHANGE_APP_STATUS: "CHANGE_APP_STATUS", CHANGE_APP_STATUS: 'CHANGE_APP_STATUS',
SEND_BROADCAST: "SEND_BROADCAST", SEND_BROADCAST: 'SEND_BROADCAST',
CHANGE_LOCAL_SETTINGS: "CHANGE_LOCAL_SETTINGS", CHANGE_LOCAL_SETTINGS: 'CHANGE_LOCAL_SETTINGS',
GNB_OPENED: "GNB_OPENED", GNB_OPENED: 'GNB_OPENED',
SET_SHOW_POPUP: "SET_SHOW_POPUP", SET_SHOW_POPUP: 'SET_SHOW_POPUP',
SET_SHOW_SECONDARY_POPUP: "SET_SHOW_SECONDARY_POPUP", SET_SHOW_SECONDARY_POPUP: 'SET_SHOW_SECONDARY_POPUP',
SET_HIDE_POPUP: "SET_HIDE_POPUP", SET_HIDE_POPUP: 'SET_HIDE_POPUP',
SET_HIDE_SECONDARY_POPUP: "SET_HIDE_SECONDARY_POPUP", SET_HIDE_SECONDARY_POPUP: 'SET_HIDE_SECONDARY_POPUP',
SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: "SHOW_OPTIONAL_TERMS_CONFIRM_POPUP", SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: 'SHOW_OPTIONAL_TERMS_CONFIRM_POPUP',
HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: "HIDE_OPTIONAL_TERMS_CONFIRM_POPUP", HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: 'HIDE_OPTIONAL_TERMS_CONFIRM_POPUP',
TOGGLE_OPTIONAL_TERMS_CONFIRM: "TOGGLE_OPTIONAL_TERMS_CONFIRM", TOGGLE_OPTIONAL_TERMS_CONFIRM: 'TOGGLE_OPTIONAL_TERMS_CONFIRM',
// 선택약관 팝업 상태 관리 // 선택약관 팝업 상태 관리
SET_OPTIONAL_TERMS_POPUP_SHOWN: "SET_OPTIONAL_TERMS_POPUP_SHOWN", SET_OPTIONAL_TERMS_POPUP_SHOWN: 'SET_OPTIONAL_TERMS_POPUP_SHOWN',
SET_OPTIONAL_TERMS_USER_DECISION: "SET_OPTIONAL_TERMS_USER_DECISION", SET_OPTIONAL_TERMS_USER_DECISION: 'SET_OPTIONAL_TERMS_USER_DECISION',
RESET_OPTIONAL_TERMS_SESSION: "RESET_OPTIONAL_TERMS_SESSION", RESET_OPTIONAL_TERMS_SESSION: 'RESET_OPTIONAL_TERMS_SESSION',
// 선택약관 직접 상태 업데이트 (API 호출 없이) // 선택약관 직접 상태 업데이트 (API 호출 없이)
UPDATE_OPTIONAL_TERMS_AGREE_DIRECT: "UPDATE_OPTIONAL_TERMS_AGREE_DIRECT", UPDATE_OPTIONAL_TERMS_AGREE_DIRECT: 'UPDATE_OPTIONAL_TERMS_AGREE_DIRECT',
UPDATE_TERMS_AGREEMENT_STATUS_DIRECT: "UPDATE_TERMS_AGREEMENT_STATUS_DIRECT", UPDATE_TERMS_AGREEMENT_STATUS_DIRECT: 'UPDATE_TERMS_AGREEMENT_STATUS_DIRECT',
SET_EXIT_APP: "SET_EXIT_APP", SET_EXIT_APP: 'SET_EXIT_APP',
GET_LOGIN_USER_DATA: "GET_LOGIN_USER_DATA", GET_LOGIN_USER_DATA: 'GET_LOGIN_USER_DATA',
GET_TERMS_AGREE_YN: "GET_TERMS_AGREE_YN", GET_TERMS_AGREE_YN: 'GET_TERMS_AGREE_YN',
LAUNCH_MEMBERSHIP_APP: "LAUNCH_MEMBERSHIP_APP", LAUNCH_MEMBERSHIP_APP: 'LAUNCH_MEMBERSHIP_APP',
SET_FOCUS: "SET_FOCUS", SET_FOCUS: 'SET_FOCUS',
SET_GNB_MENU: "SET_GNB_MENU", SET_GNB_MENU: 'SET_GNB_MENU',
SET_SYSTEM_NOTICE: "SET_SYSTEM_NOTICE", SET_SYSTEM_NOTICE: 'SET_SYSTEM_NOTICE',
SET_SYSTEM_TERMINATION: "SET_SYSTEM_TERMINATION", SET_SYSTEM_TERMINATION: 'SET_SYSTEM_TERMINATION',
SET_DEEP_LINK: "SET_DEEP_LINK", SET_DEEP_LINK: 'SET_DEEP_LINK',
SET_SECOND_LAYER_INFO: "SET_SECOND_LAYER_INFO", SET_SECOND_LAYER_INFO: 'SET_SECOND_LAYER_INFO',
SET_ERROR_MESSAGE: "SET_ERROR_MESSAGE", SET_ERROR_MESSAGE: 'SET_ERROR_MESSAGE',
CLEAR_ERROR_MESSAGE: "CLEAR_ERROR_MESSAGE", CLEAR_ERROR_MESSAGE: 'CLEAR_ERROR_MESSAGE',
GET_DEVICE_MACADDRESS: "GET_DEVICE_MACADDRESS", GET_DEVICE_MACADDRESS: 'GET_DEVICE_MACADDRESS',
SET_DEVICE_REGISTERED: "SET_DEVICE_REGISTERED", SET_DEVICE_REGISTERED: 'SET_DEVICE_REGISTERED',
// billing actions // billing actions
GET_MY_INFO_BILLING_SEARCH: "GET_MY_INFO_BILLING_SEARCH", GET_MY_INFO_BILLING_SEARCH: 'GET_MY_INFO_BILLING_SEARCH',
// card actions // card actions
GET_MY_INFO_CARD_SEARCH: "GET_MY_INFO_CARD_SEARCH", GET_MY_INFO_CARD_SEARCH: 'GET_MY_INFO_CARD_SEARCH',
// shipping actions // shipping actions
GET_MY_INFO_SHIPPING_SEARCH: "GET_MY_INFO_SHIPPING_SEARCH", GET_MY_INFO_SHIPPING_SEARCH: 'GET_MY_INFO_SHIPPING_SEARCH',
// cart actions // cart actions
GET_MY_INFO_CART_SEARCH: "GET_MY_INFO_CART_SEARCH", GET_MY_INFO_CART_SEARCH: 'GET_MY_INFO_CART_SEARCH',
ADD_TO_CART: "ADD_TO_CART", ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: "REMOVE_FROM_CART", REMOVE_FROM_CART: 'REMOVE_FROM_CART',
UPDATE_CART_ITEM: "UPDATE_CART_ITEM", UPDATE_CART_ITEM: 'UPDATE_CART_ITEM',
CLEAR_CART: "CLEAR_CART", CLEAR_CART: 'CLEAR_CART',
// appData actions // appData actions
ADD_MAIN_INDEX: "ADD_MAIN_INDEX", ADD_MAIN_INDEX: 'ADD_MAIN_INDEX',
SEND_SMS: "SEND_SMS", SEND_SMS: 'SEND_SMS',
CLEAR_SMS: "CLEAR_SMS", CLEAR_SMS: 'CLEAR_SMS',
// home actions // home actions
GET_HOME_TERMS: "GET_HOME_TERMS", GET_HOME_TERMS: 'GET_HOME_TERMS',
SET_TERMS_ID_MAP: "SET_TERMS_ID_MAP", SET_TERMS_ID_MAP: 'SET_TERMS_ID_MAP',
SET_OPTIONAL_TERMS_AVAILABILITY: "SET_OPTIONAL_TERMS_AVAILABILITY", SET_OPTIONAL_TERMS_AVAILABILITY: 'SET_OPTIONAL_TERMS_AVAILABILITY',
GET_HOME_MENU: "GET_HOME_MENU", GET_HOME_MENU: 'GET_HOME_MENU',
GET_HOME_LAYOUT: "GET_HOME_LAYOUT", GET_HOME_LAYOUT: 'GET_HOME_LAYOUT',
GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS", GET_HOME_MAIN_CONTENTS: 'GET_HOME_MAIN_CONTENTS',
GET_HOME_FULL_VIDEO_INFO: "GET_HOME_FULL_VIDEO_INFO", GET_HOME_FULL_VIDEO_INFO: 'GET_HOME_FULL_VIDEO_INFO',
GET_THEME_CURATION_INFO: "GET_THEME_CURATION_INFO", GET_THEME_CURATION_INFO: 'GET_THEME_CURATION_INFO',
GET_THEME_CURATION_DETAIL_INFO: "GET_THEME_CURATION_DETAIL_INFO", GET_THEME_CURATION_DETAIL_INFO: 'GET_THEME_CURATION_DETAIL_INFO',
GET_THEME_HOTEL_DETAIL_INFO: "GET_THEME_HOTEL_DETAIL_INFO", GET_THEME_HOTEL_DETAIL_INFO: 'GET_THEME_HOTEL_DETAIL_INFO',
GET_THEME_MENU_SHELF_INFO: "GET_THEME_MENU_SHELF_INFO", GET_THEME_MENU_SHELF_INFO: 'GET_THEME_MENU_SHELF_INFO',
CLEAR_THEME_MENU_SHELF_INFO: "CLEAR_THEME_MENU_SHELF_INFO", CLEAR_THEME_MENU_SHELF_INFO: 'CLEAR_THEME_MENU_SHELF_INFO',
CLEAR_THEME_DETAIL: "CLEAR_THEME_DETAIL", CLEAR_THEME_DETAIL: 'CLEAR_THEME_DETAIL',
CHECK_ENTER_THROUGH_GNB: "CHECK_ENTER_THROUGH_GNB", CHECK_ENTER_THROUGH_GNB: 'CHECK_ENTER_THROUGH_GNB',
SET_DEFAULT_FOCUS: "SET_DEFAULT_FOCUS", SET_DEFAULT_FOCUS: 'SET_DEFAULT_FOCUS',
SET_BANNER_INDEX: "SET_BANNER_INDEX", SET_BANNER_INDEX: 'SET_BANNER_INDEX',
RESET_HOME_INFO: "RESET_HOME_INFO", RESET_HOME_INFO: 'RESET_HOME_INFO',
UPDATE_HOME_INFO: "UPDATE_HOME_INFO", UPDATE_HOME_INFO: 'UPDATE_HOME_INFO',
// brand actions // brand actions
GET_BRAND_LIST: "GET_BRAND_LIST", GET_BRAND_LIST: 'GET_BRAND_LIST',
GET_BRAND_LAYOUT_INFO: "GET_BRAND_LAYOUT_INFO", GET_BRAND_LAYOUT_INFO: 'GET_BRAND_LAYOUT_INFO',
GET_BRAND_LIVE_CHANNEL_INFO: "GET_BRAND_LIVE_CHANNEL_INFO", GET_BRAND_LIVE_CHANNEL_INFO: 'GET_BRAND_LIVE_CHANNEL_INFO',
GET_BRAND_CHAN_INFO: "GET_BRAND_CHAN_INFO", GET_BRAND_CHAN_INFO: 'GET_BRAND_CHAN_INFO',
GET_BRAND_TSV_INFO: "GET_BRAND_TSV_INFO", GET_BRAND_TSV_INFO: 'GET_BRAND_TSV_INFO',
GET_BRAND_RECOMMENDED_SHOW_INFO: "GET_BRAND_RECOMMENDED_SHOW_INFO", GET_BRAND_RECOMMENDED_SHOW_INFO: 'GET_BRAND_RECOMMENDED_SHOW_INFO',
GET_BRAND_SERIES_INFO: "GET_BRAND_SERIES_INFO", GET_BRAND_SERIES_INFO: 'GET_BRAND_SERIES_INFO',
GET_BRAND_CATEGORY_INFO: "GET_BRAND_CATEGORY_INFO", GET_BRAND_CATEGORY_INFO: 'GET_BRAND_CATEGORY_INFO',
GET_BRAND_CATEGORY_PRODUCT_INFO: "GET_BRAND_CATEGORY_PRODUCT_INFO", GET_BRAND_CATEGORY_PRODUCT_INFO: 'GET_BRAND_CATEGORY_PRODUCT_INFO',
GET_BRAND_BEST_SELLER: "GET_BRAND_BEST_SELLER", GET_BRAND_BEST_SELLER: 'GET_BRAND_BEST_SELLER',
GET_BRAND_CREATORS_INFO: "GET_BRAND_CREATORS_INFO", GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
GET_BRAND_SHOWROOM: "GET_BRAND_SHOWROOM", GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
GET_BRAND_RECENTLY_AIRED: "GET_BRAND_RECENTLY_AIRED", GET_BRAND_RECENTLY_AIRED: 'GET_BRAND_RECENTLY_AIRED',
SET_BRAND_LIVE_CHANNEL_UPCOMING: "SET_BRAND_LIVE_CHANNEL_UPCOMING", SET_BRAND_LIVE_CHANNEL_UPCOMING: 'SET_BRAND_LIVE_CHANNEL_UPCOMING',
SET_BRAND_CHAN_INFO: "SET_BRAND_CHAN_INFO", SET_BRAND_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
RESET_BRAND_STATE: "RESET_BRAND_STATE", RESET_BRAND_STATE: 'RESET_BRAND_STATE',
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: "RESET_BRAND_STATE_EXCEPT_BRAND_INFO", RESET_BRAND_STATE_EXCEPT_BRAND_INFO: 'RESET_BRAND_STATE_EXCEPT_BRAND_INFO',
RESET_BRAND_LAYOUT_INFO: "RESET_BRAND_LAYOUT_INFO", RESET_BRAND_LAYOUT_INFO: 'RESET_BRAND_LAYOUT_INFO',
// main actions // main actions
GET_SUB_CATEGORY: "GET_SUB_CATEGORY", GET_SUB_CATEGORY: 'GET_SUB_CATEGORY',
APPEND_SUB_CATEGORY: "APPEND_SUB_CATEGORY", APPEND_SUB_CATEGORY: 'APPEND_SUB_CATEGORY',
GET_TOP_20_SHOW: "GET_TOP_20_SHOW", GET_TOP_20_SHOW: 'GET_TOP_20_SHOW',
GET_PRODUCT_DETAIL: "GET_PRODUCT_DETAIL", GET_PRODUCT_DETAIL: 'GET_PRODUCT_DETAIL',
GET_YOUMAYLIKE: "GET_YOUMAYLIKE", GET_YOUMAYLIKE: 'GET_YOUMAYLIKE',
SET_MAIN_LIKE_CATEGORY: "SET_MAIN_LIKE_CATEGORY", SET_MAIN_LIKE_CATEGORY: 'SET_MAIN_LIKE_CATEGORY',
SET_MAIN_LIVE_UPCOMING_ALARM: "SET_MAIN_LIVE_UPCOMING_ALARM", SET_MAIN_LIVE_UPCOMING_ALARM: 'SET_MAIN_LIVE_UPCOMING_ALARM',
GET_MAIN_LIVE_SHOW_NOW_PRODUCT: "GET_MAIN_LIVE_SHOW_NOW_PRODUCT", GET_MAIN_LIVE_SHOW_NOW_PRODUCT: 'GET_MAIN_LIVE_SHOW_NOW_PRODUCT',
GET_MAIN_LIVE_SHOW: "GET_MAIN_LIVE_SHOW", GET_MAIN_LIVE_SHOW: 'GET_MAIN_LIVE_SHOW',
GET_MAIN_CATEGORY_SHOW_DETAIL: "GET_MAIN_CATEGORY_SHOW_DETAIL", GET_MAIN_CATEGORY_SHOW_DETAIL: 'GET_MAIN_CATEGORY_SHOW_DETAIL',
CLEAR_PRODUCT_DETAIL: "CLEAR_PRODUCT_DETAIL", CLEAR_PRODUCT_DETAIL: 'CLEAR_PRODUCT_DETAIL',
CLEAR_SUB_CATEGORY: "CLEAR_SUB_CATEGORY", CLEAR_SUB_CATEGORY: 'CLEAR_SUB_CATEGORY',
CLEAR_SHOPNOW_INFO: "CLEAR_SHOPNOW_INFO", CLEAR_SHOPNOW_INFO: 'CLEAR_SHOPNOW_INFO',
// myPage actions // myPage actions
GET_MY_RECOMMANDED_KEYWORD: "GET_MY_RECOMMANDED_KEYWORD", GET_MY_RECOMMANDED_KEYWORD: 'GET_MY_RECOMMANDED_KEYWORD',
GET_MY_FAQ_INFO: "GET_MY_FAQ_INFO", GET_MY_FAQ_INFO: 'GET_MY_FAQ_INFO',
GET_NOTICE: "GET_NOTICE", GET_NOTICE: 'GET_NOTICE',
GET_MY_CUSTOMERS: "GET_MY_CUSTOMERS", GET_MY_CUSTOMERS: 'GET_MY_CUSTOMERS',
GET_MY_FAVORITE: "GET_MY_FAVORITE", GET_MY_FAVORITE: 'GET_MY_FAVORITE',
DELETE_MY_FAVORITE: "DELETE_MY_FAVORITE", DELETE_MY_FAVORITE: 'DELETE_MY_FAVORITE',
GET_MY_FAVORITE_FLAG: "GET_MY_FAVORITE_FLAG", GET_MY_FAVORITE_FLAG: 'GET_MY_FAVORITE_FLAG',
SET_MY_TERMS_WITHDRAW: "SET_MY_TERMS_WITHDRAW", SET_MY_TERMS_WITHDRAW: 'SET_MY_TERMS_WITHDRAW',
GET_MY_UPCOMING_CHANGE_INFO: "GET_MY_UPCOMING_CHANGE_INFO", GET_MY_UPCOMING_CHANGE_INFO: 'GET_MY_UPCOMING_CHANGE_INFO',
GET_MY_UPCOMING_ALERT_SHOW: "GET_MY_UPCOMING_ALERT_SHOW", GET_MY_UPCOMING_ALERT_SHOW: 'GET_MY_UPCOMING_ALERT_SHOW',
DELETE_MY_UPCOMING_ALERT_SHOW: "DELETE_MY_UPCOMING_ALERT_SHOW", DELETE_MY_UPCOMING_ALERT_SHOW: 'DELETE_MY_UPCOMING_ALERT_SHOW',
GET_MY_UPCOMING_ALERT_SHOW_KEYS: "GET_MY_UPCOMING_ALERT_SHOW_KEYS", GET_MY_UPCOMING_ALERT_SHOW_KEYS: 'GET_MY_UPCOMING_ALERT_SHOW_KEYS',
SET_MY_UPCOMING_USE_ALERT: "SET_MY_UPCOMING_USE_ALERT", SET_MY_UPCOMING_USE_ALERT: 'SET_MY_UPCOMING_USE_ALERT',
GET_UPCOMING_ALERT_SHOW_CHANGE_INFO: "GET_UPCOMING_ALERT_SHOW_CHANGE_INFO", GET_UPCOMING_ALERT_SHOW_CHANGE_INFO: 'GET_UPCOMING_ALERT_SHOW_CHANGE_INFO',
GET_MY_RECENTLY_VIEWED_INFO: "GET_MY_RECENTLY_VIEWED_INFO", GET_MY_RECENTLY_VIEWED_INFO: 'GET_MY_RECENTLY_VIEWED_INFO',
CLEAR_RECENTLY_VIEWED_INFO: "CLEAR_RECENTLY_VIEWED_INFO", CLEAR_RECENTLY_VIEWED_INFO: 'CLEAR_RECENTLY_VIEWED_INFO',
CLEAR_FAVORITES: "CLEAR_FAVORITES", CLEAR_FAVORITES: 'CLEAR_FAVORITES',
SET_MYPAGE_TERMS_AGREE: "SET_MYPAGE_TERMS_AGREE", SET_MYPAGE_TERMS_AGREE: 'SET_MYPAGE_TERMS_AGREE',
SET_MYPAGE_TERMS_AGREE_SUCCESS: "SET_MYPAGE_TERMS_AGREE_SUCCESS", SET_MYPAGE_TERMS_AGREE_SUCCESS: 'SET_MYPAGE_TERMS_AGREE_SUCCESS',
SET_MYPAGE_TERMS_AGREE_FAIL: "SET_MYPAGE_TERMS_AGREE_FAIL", SET_MYPAGE_TERMS_AGREE_FAIL: 'SET_MYPAGE_TERMS_AGREE_FAIL',
// onSale actions // onSale actions
GET_HOME_ON_SALE_INFO: "GET_HOME_ON_SALE_INFO", GET_HOME_ON_SALE_INFO: 'GET_HOME_ON_SALE_INFO',
GET_ON_SALE_INFO: "GET_ON_SALE_INFO", GET_ON_SALE_INFO: 'GET_ON_SALE_INFO',
COPY_CATEGORY_INFO: "COPY_CATEGORY_INFO", COPY_CATEGORY_INFO: 'COPY_CATEGORY_INFO',
RESET_ON_SALE_STATE: "RESET_ON_SALE_STATE", RESET_ON_SALE_STATE: 'RESET_ON_SALE_STATE',
// product actions // product actions
GET_BEST_SELLER: "GET_BEST_SELLER", GET_BEST_SELLER: 'GET_BEST_SELLER',
GET_PRODUCT_GROUP: "GET_PRODUCT_GROUP", GET_PRODUCT_GROUP: 'GET_PRODUCT_GROUP',
GET_PRODUCT_OPTION: "GET_PRODUCT_OPTION", GET_PRODUCT_OPTION: 'GET_PRODUCT_OPTION',
GET_PRODUCT_IMAGE_LENGTH: "GET_PRODUCT_IMAGE_LENGTH", GET_PRODUCT_IMAGE_LENGTH: 'GET_PRODUCT_IMAGE_LENGTH',
GET_VIDEO_INDECATOR_FOCUS: "GET_VIDEO_INDECATOR_FOCUS", GET_VIDEO_INDECATOR_FOCUS: 'GET_VIDEO_INDECATOR_FOCUS',
GET_PRODUCT_OPTION_ID: "GET_PRODUCT_OPTION_ID", GET_PRODUCT_OPTION_ID: 'GET_PRODUCT_OPTION_ID',
CLEAR_PRODUCT_OPTIONS: "CLEAR_PRODUCT_OPTIONS", CLEAR_PRODUCT_OPTIONS: 'CLEAR_PRODUCT_OPTIONS',
GET_USER_REVIEW: "GET_USER_REVIEW", GET_USER_REVIEW: 'GET_USER_REVIEW',
TOGGLE_SHOW_ALL_REVIEWS: "TOGGLE_SHOW_ALL_REVIEWS", TOGGLE_SHOW_ALL_REVIEWS: 'TOGGLE_SHOW_ALL_REVIEWS',
RESET_SHOW_ALL_REVIEWS: "RESET_SHOW_ALL_REVIEWS", RESET_SHOW_ALL_REVIEWS: 'RESET_SHOW_ALL_REVIEWS',
// search actions // search actions
GET_SEARCH: "GET_SEARCH", GET_SEARCH: 'GET_SEARCH',
GET_SHOPPERHOUSE_SEARCH: "GET_SHOPPERHOUSE_SEARCH", GET_SHOPPERHOUSE_SEARCH: 'GET_SHOPPERHOUSE_SEARCH',
RESET_SEARCH: "RESET_SEARCH", RESET_SEARCH: 'RESET_SEARCH',
GET_SEARCH_PROCESSED: "GET_SEARCH_PROCESSED", GET_SEARCH_PROCESSED: 'GET_SEARCH_PROCESSED',
SET_SEARCH_INIT_PERFORMED: "SET_SEARCH_INIT_PERFORMED", SET_SEARCH_INIT_PERFORMED: 'SET_SEARCH_INIT_PERFORMED',
UPDATE_SEARCH_TIMESTAMP: "UPDATE_SEARCH_TIMESTAMP", UPDATE_SEARCH_TIMESTAMP: 'UPDATE_SEARCH_TIMESTAMP',
// event actions // event actions
GET_WELCOME_EVENT_INFO: "GET_WELCOME_EVENT_INFO", GET_WELCOME_EVENT_INFO: 'GET_WELCOME_EVENT_INFO',
GET_EVENT_ISSUED_STATUS: "GET_EVENT_ISSUED_STATUS", GET_EVENT_ISSUED_STATUS: 'GET_EVENT_ISSUED_STATUS',
SET_EVENT_ISSUE_REQ: "SET_EVENT_ISSUE_REQ", SET_EVENT_ISSUE_REQ: 'SET_EVENT_ISSUE_REQ',
SET_EVENT_POP_CLICK_INFO: "SET_EVENT_POP_CLICK_INFO", SET_EVENT_POP_CLICK_INFO: 'SET_EVENT_POP_CLICK_INFO',
CLEAR_EVENT_INFO: "CLEAR_EVENT_INFO", CLEAR_EVENT_INFO: 'CLEAR_EVENT_INFO',
SKIP_CLICK_INFO: "SKIP_CLICK_INFO", SKIP_CLICK_INFO: 'SKIP_CLICK_INFO',
CLEAR_CURATION_COUPON: "CLEAR_CURATION_COUPON", CLEAR_CURATION_COUPON: 'CLEAR_CURATION_COUPON',
CLEAR_EVENT_ISSUED_STATUS_SUCCEESS: "CLEAR_EVENT_ISSUED_STATUS_SUCCEESS", CLEAR_EVENT_ISSUED_STATUS_SUCCEESS: 'CLEAR_EVENT_ISSUED_STATUS_SUCCEESS',
// coupon actions // coupon actions
GET_PRODUCT_COUPON_INFO: "GET_PRODUCT_COUPON_INFO", GET_PRODUCT_COUPON_INFO: 'GET_PRODUCT_COUPON_INFO',
GET_PRODUCT_COUPON_SEARCH: "GET_PRODUCT_COUPON_SEARCH", GET_PRODUCT_COUPON_SEARCH: 'GET_PRODUCT_COUPON_SEARCH',
GET_PRODUCT_COUPON_DOWNLOAD: "GET_PRODUCT_COUPON_DOWNLOAD", GET_PRODUCT_COUPON_DOWNLOAD: 'GET_PRODUCT_COUPON_DOWNLOAD',
GET_PRODUCT_COUPON_TOTDOWNLOAD: "GET_PRODUCT_COUPON_TOTDOWNLOAD", GET_PRODUCT_COUPON_TOTDOWNLOAD: 'GET_PRODUCT_COUPON_TOTDOWNLOAD',
CLEAR_COUPON_INFO: "CLEAR_COUPON_INFO", CLEAR_COUPON_INFO: 'CLEAR_COUPON_INFO',
CLEAR_GET_PRODUCT_COUPON_DOWNLOAD: "CLEAR_GET_PRODUCT_COUPON_DOWNLOAD", CLEAR_GET_PRODUCT_COUPON_DOWNLOAD: 'CLEAR_GET_PRODUCT_COUPON_DOWNLOAD',
// checkout actions // checkout actions
GET_CHECKOUT_INFO: "GET_CHECKOUT_INFO", GET_CHECKOUT_INFO: 'GET_CHECKOUT_INFO',
INSERT_MY_INFO_CHECKOUT_ORDER: "INSERT_MY_INFO_CHECKOUT_ORDER", INSERT_MY_INFO_CHECKOUT_ORDER: 'INSERT_MY_INFO_CHECKOUT_ORDER',
GET_TAX_INFOS: "GET_TAX_INFOS", GET_TAX_INFOS: 'GET_TAX_INFOS',
UPDATE_SELECTED_SHIPPING_ADDR: "UPDATE_SELECTED_SHIPPING_ADDR", UPDATE_SELECTED_SHIPPING_ADDR: 'UPDATE_SELECTED_SHIPPING_ADDR',
UPDATE_SELECTED_BILLING_ADDR: "UPDATE_SELECTED_BILLING_ADDR", UPDATE_SELECTED_BILLING_ADDR: 'UPDATE_SELECTED_BILLING_ADDR',
UPDATE_SELECTED_COUPON: "UPDATE_SELECTED_COUPON", UPDATE_SELECTED_COUPON: 'UPDATE_SELECTED_COUPON',
GET_CHECKOUT_TOTAL_AMT: "GET_CHECKOUT_TOTAL_AMT", GET_CHECKOUT_TOTAL_AMT: 'GET_CHECKOUT_TOTAL_AMT',
CHECKOUT_DATA_RESET: "CHECKOUT_DATA_RESET", CHECKOUT_DATA_RESET: 'CHECKOUT_DATA_RESET',
// order actions // order actions
SET_PURCHASE_TERMS_AGREE: "SET_PURCHASE_TERMS_AGREE", SET_PURCHASE_TERMS_AGREE: 'SET_PURCHASE_TERMS_AGREE',
SET_PURCHASE_TERMS_WITHDRAW: "SET_PURCHASE_TERMS_WITHDRAW", SET_PURCHASE_TERMS_WITHDRAW: 'SET_PURCHASE_TERMS_WITHDRAW',
GET_MY_INFO_ORDER_SEARCH: "GET_MY_INFO_ORDER_SEARCH", GET_MY_INFO_ORDER_SEARCH: 'GET_MY_INFO_ORDER_SEARCH',
GET_MY_INFO_ORDER_SEARCH_CANCEL: "GET_MY_INFO_ORDER_SEARCH_CANCEL", GET_MY_INFO_ORDER_SEARCH_CANCEL: 'GET_MY_INFO_ORDER_SEARCH_CANCEL',
GET_MY_INFO_ORDER_DETAIL_SEARCH: "GET_MY_INFO_ORDER_DETAIL_SEARCH", GET_MY_INFO_ORDER_DETAIL_SEARCH: 'GET_MY_INFO_ORDER_DETAIL_SEARCH',
GET_MY_INFO_ORDER_SHIPPING_SEARCH: "GET_MY_INFO_ORDER_SHIPPING_SEARCH", GET_MY_INFO_ORDER_SHIPPING_SEARCH: 'GET_MY_INFO_ORDER_SHIPPING_SEARCH',
CLEAR_MY_INFO_ORDER_SEARCH: "CLEAR_MY_INFO_ORDER_SEARCH", CLEAR_MY_INFO_ORDER_SEARCH: 'CLEAR_MY_INFO_ORDER_SEARCH',
// play controller // play controller
GET_CHAT_LOG: "GET_CHAT_LOG", GET_CHAT_LOG: 'GET_CHAT_LOG',
GET_SUBTITLE: "GET_SUBTITLE", GET_SUBTITLE: 'GET_SUBTITLE',
CLEAR_PLAYER_INFO: "CLEAR_PLAYER_INFO", CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO',
UPDATE_VIDEO_PLAY_STATE: "UPDATE_VIDEO_PLAY_STATE", UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE',
// 🔽 [추가] 플레이 제어 매니저 액션 타입 // 🔽 [추가] 플레이 제어 매니저 액션 타입
/** /**
@@ -226,88 +226,88 @@ export const types = {
* SET_PLAYER_CONTROL: 특정 컴포넌트에게 비디오 재생 제어권을 부여합니다. * SET_PLAYER_CONTROL: 특정 컴포넌트에게 비디오 재생 제어권을 부여합니다.
* CLEAR_PLAYER_CONTROL: 컴포넌트로부터 비디오 재생 제어권을 회수합니다. * CLEAR_PLAYER_CONTROL: 컴포넌트로부터 비디오 재생 제어권을 회수합니다.
*/ */
SET_PLAYER_CONTROL: "SET_PLAYER_CONTROL", SET_PLAYER_CONTROL: 'SET_PLAYER_CONTROL',
CLEAR_PLAYER_CONTROL: "CLEAR_PLAYER_CONTROL", CLEAR_PLAYER_CONTROL: 'CLEAR_PLAYER_CONTROL',
// reset action // reset action
RESET_REDUX_STATE: "RESET_REDUX_STATE", RESET_REDUX_STATE: 'RESET_REDUX_STATE',
// cancel actions // cancel actions
GET_MY_INFO_ORDER_CANCEL_SEARCH: "GET_MY_INFO_ORDER_CANCEL_SEARCH", GET_MY_INFO_ORDER_CANCEL_SEARCH: 'GET_MY_INFO_ORDER_CANCEL_SEARCH',
GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH: GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH: 'GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH',
"GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH", UPDATE_ORDER_PARTIAL_CANCEL: 'UPDATE_ORDER_PARTIAL_CANCEL',
UPDATE_ORDER_PARTIAL_CANCEL: "UPDATE_ORDER_PARTIAL_CANCEL", PAYMENT_TOTAL_CANCEL: 'PAYMENT_TOTAL_CANCEL',
PAYMENT_TOTAL_CANCEL: "PAYMENT_TOTAL_CANCEL",
// emp actions // emp actions
GET_SHOPTIME_TERMS: "GET_SHOPTIME_TERMS", GET_SHOPTIME_TERMS: 'GET_SHOPTIME_TERMS',
// pinCode actions // pinCode actions
GET_MY_INFO_CARD_PINCODE_CHECK: "GET_MY_INFO_CARD_PINCODE_CHECK", GET_MY_INFO_CARD_PINCODE_CHECK: 'GET_MY_INFO_CARD_PINCODE_CHECK',
// new actions // new actions
CANCEL_FOCUS_ELEMENT: "CANCEL_FOCUS_ELEMENT", CANCEL_FOCUS_ELEMENT: 'CANCEL_FOCUS_ELEMENT',
// 약관동의 여부 확인 상태 // 약관동의 여부 확인 상태
GET_TERMS_AGREE_YN_START: "GET_TERMS_AGREE_YN_START", GET_TERMS_AGREE_YN_START: 'GET_TERMS_AGREE_YN_START',
GET_TERMS_AGREE_YN_SUCCESS: "GET_TERMS_AGREE_YN_SUCCESS", GET_TERMS_AGREE_YN_SUCCESS: 'GET_TERMS_AGREE_YN_SUCCESS',
GET_TERMS_AGREE_YN_FAILURE: "GET_TERMS_AGREE_YN_FAILURE", GET_TERMS_AGREE_YN_FAILURE: 'GET_TERMS_AGREE_YN_FAILURE',
// device // device
REQ_REG_DEVICE_INFO: "REQ_REG_DEVICE_INFO", REQ_REG_DEVICE_INFO: 'REQ_REG_DEVICE_INFO',
// 🔽 [추가] 영구재생 비디오 정보 저장 // 🔽 [추가] 영구재생 비디오 정보 저장
SET_PERSISTENT_VIDEO_INFO: "SET_PERSISTENT_VIDEO_INFO", SET_PERSISTENT_VIDEO_INFO: 'SET_PERSISTENT_VIDEO_INFO',
// 🔽 [추가] 배너 비디오 제어 액션 타입 // 🔽 [추가] 배너 비디오 제어 액션 타입
/** /**
* HomeBanner의 배너 간 비디오 재생 제어를 위한 액션 타입들. * HomeBanner의 배너 간 비디오 재생 제어를 위한 액션 타입들.
* 첫 번째 배너 상시 재생과 두 번째 배너 포커스 재생을 관리합니다. * 첫 번째 배너 상시 재생과 두 번째 배너 포커스 재생을 관리합니다.
*/ */
SET_BANNER_STATE: "SET_BANNER_STATE", SET_BANNER_STATE: 'SET_BANNER_STATE',
SET_BANNER_FOCUS: "SET_BANNER_FOCUS", SET_BANNER_FOCUS: 'SET_BANNER_FOCUS',
SET_BANNER_AVAILABILITY: "SET_BANNER_AVAILABILITY", SET_BANNER_AVAILABILITY: 'SET_BANNER_AVAILABILITY',
SET_BANNER_TRANSITION: "SET_BANNER_TRANSITION", SET_BANNER_TRANSITION: 'SET_BANNER_TRANSITION',
PAUSE_PLAYER_CONTROL: "PAUSE_PLAYER_CONTROL", PAUSE_PLAYER_CONTROL: 'PAUSE_PLAYER_CONTROL',
RESUME_PLAYER_CONTROL: "RESUME_PLAYER_CONTROL", RESUME_PLAYER_CONTROL: 'RESUME_PLAYER_CONTROL',
// 🔽 [추가] HomeBanner 동영상 포커스 정책 관리 // 🔽 [추가] HomeBanner 동영상 포커스 정책 관리
SET_CURRENT_FOCUS_BANNER: "SET_CURRENT_FOCUS_BANNER", SET_CURRENT_FOCUS_BANNER: 'SET_CURRENT_FOCUS_BANNER',
UPDATE_VIDEO_POLICY: "UPDATE_VIDEO_POLICY", UPDATE_VIDEO_POLICY: 'UPDATE_VIDEO_POLICY',
SET_MODAL_BORDER: "SET_MODAL_BORDER", SET_MODAL_BORDER: 'SET_MODAL_BORDER',
SET_BANNER_VISIBILITY: "SET_BANNER_VISIBILITY", SET_BANNER_VISIBILITY: 'SET_BANNER_VISIBILITY',
// 🔽 [추가] JustForYou 상품 관리 부분 // 🔽 [추가] JustForYou 상품 관리 부분
GET_RECENTLY_SAW_ITEM: "GET_RECENTLY_SAW_ITEM", GET_RECENTLY_SAW_ITEM: 'GET_RECENTLY_SAW_ITEM',
GET_LIKE_BRAND_PRODUCT: "GET_LIKE_BRAND_PRODUCT", GET_LIKE_BRAND_PRODUCT: 'GET_LIKE_BRAND_PRODUCT',
GET_MORE_TO_CONCIDER_AT_THIS_PRICE: "GET_MORE_TO_CONCIDER_AT_THIS_PRICE", GET_MORE_TO_CONCIDER_AT_THIS_PRICE: 'GET_MORE_TO_CONCIDER_AT_THIS_PRICE',
GET_JUSTFORYOU_INFO: "GET_JUSTFORYOU_INFO", GET_JUSTFORYOU_INFO: 'GET_JUSTFORYOU_INFO',
// 🔽 Voice Conductor 관련 액션 타입 // 🔽 Voice Conductor 관련 액션 타입
VOICE_REGISTER_SUCCESS: "VOICE_REGISTER_SUCCESS", VOICE_REGISTER_SUCCESS: 'VOICE_REGISTER_SUCCESS',
VOICE_REGISTER_FAILURE: "VOICE_REGISTER_FAILURE", VOICE_REGISTER_FAILURE: 'VOICE_REGISTER_FAILURE',
VOICE_SET_TICKET: "VOICE_SET_TICKET", VOICE_SET_TICKET: 'VOICE_SET_TICKET',
VOICE_SET_CONTEXT_SUCCESS: "VOICE_SET_CONTEXT_SUCCESS", VOICE_SET_CONTEXT_SUCCESS: 'VOICE_SET_CONTEXT_SUCCESS',
VOICE_SET_CONTEXT_FAILURE: "VOICE_SET_CONTEXT_FAILURE", VOICE_SET_CONTEXT_FAILURE: 'VOICE_SET_CONTEXT_FAILURE',
VOICE_PERFORM_ACTION: "VOICE_PERFORM_ACTION", VOICE_PERFORM_ACTION: 'VOICE_PERFORM_ACTION',
VOICE_REPORT_RESULT_SUCCESS: "VOICE_REPORT_RESULT_SUCCESS", VOICE_REPORT_RESULT_SUCCESS: 'VOICE_REPORT_RESULT_SUCCESS',
VOICE_REPORT_RESULT_FAILURE: "VOICE_REPORT_RESULT_FAILURE", VOICE_REPORT_RESULT_FAILURE: 'VOICE_REPORT_RESULT_FAILURE',
VOICE_UPDATE_INTENTS: "VOICE_UPDATE_INTENTS", VOICE_UPDATE_INTENTS: 'VOICE_UPDATE_INTENTS',
VOICE_CLEAR_STATE: "VOICE_CLEAR_STATE", VOICE_CLEAR_STATE: 'VOICE_CLEAR_STATE',
VOICE_ADD_LOG: "VOICE_ADD_LOG", VOICE_ADD_LOG: 'VOICE_ADD_LOG',
VOICE_CLEAR_LOGS: "VOICE_CLEAR_LOGS", VOICE_CLEAR_LOGS: 'VOICE_CLEAR_LOGS',
VOICE_STT_TEXT_RECEIVED: "VOICE_STT_TEXT_RECEIVED", // STT 텍스트 수신 VOICE_STT_TEXT_RECEIVED: 'VOICE_STT_TEXT_RECEIVED', // STT 텍스트 수신
// 🔽 Web Speech API 관련 // 🔽 Web Speech API 관련
WEB_SPEECH_INITIALIZED: "WEB_SPEECH_INITIALIZED", WEB_SPEECH_INITIALIZED: 'WEB_SPEECH_INITIALIZED',
WEB_SPEECH_START: "WEB_SPEECH_START", WEB_SPEECH_START: 'WEB_SPEECH_START',
WEB_SPEECH_INTERIM_RESULT: "WEB_SPEECH_INTERIM_RESULT", WEB_SPEECH_INTERIM_RESULT: 'WEB_SPEECH_INTERIM_RESULT',
WEB_SPEECH_END: "WEB_SPEECH_END", WEB_SPEECH_END: 'WEB_SPEECH_END',
WEB_SPEECH_ERROR: "WEB_SPEECH_ERROR", WEB_SPEECH_ERROR: 'WEB_SPEECH_ERROR',
WEB_SPEECH_CLEANUP: "WEB_SPEECH_CLEANUP", WEB_SPEECH_CLEANUP: 'WEB_SPEECH_CLEANUP',
//convert img //convert img
CONVERT_PDF_TO_IMAGE: "CONVERT_PDF_TO_IMAGE", CONVERT_PDF_TO_IMAGE: 'CONVERT_PDF_TO_IMAGE',
CONVERT_PDF_TO_IMAGE_SUCCESS: "CONVERT_PDF_TO_IMAGE_SUCCESS", CONVERT_PDF_TO_IMAGE_SUCCESS: 'CONVERT_PDF_TO_IMAGE_SUCCESS',
CONVERT_PDF_TO_IMAGE_FAILURE: "CONVERT_PDF_TO_IMAGE_FAILURE", CONVERT_PDF_TO_IMAGE_FAILURE: 'CONVERT_PDF_TO_IMAGE_FAILURE',
CLEAR_CONVERTED_IMAGE: "CLEAR_CONVERTED_IMAGE", CLEAR_CONVERTED_IMAGE: 'CLEAR_CONVERTED_IMAGE',
CLEAR_CONVERTED_IMAGE_BY_URL: 'CLEAR_CONVERTED_IMAGE_BY_URL', // ⭐ 새로 추가
}; };

View File

@@ -1,72 +1,137 @@
import { URLS } from '../api/apiConfig'; import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios'; import { TAxios } from '../api/TAxios';
import { get } from '../utils/fp';
import { types } from './actionTypes'; import { types } from './actionTypes';
import { changeAppStatus } from './commonActions';
/** /**
* PDF를 이미지로 변환하는 액션 * PDF를 이미지로 변환
* @param {string} pdfUrl - 변환할 PDF URL * @param {string} pdfUrl - 변환할 PDF URL
* @param {function} callback - 성공/실패 후 실행할 콜백 함수 * @param {function} callback - 성공/실패 후 실행할 콜백 (error, imageUrl)
*/ */
export const convertPdfToImage = (pdfUrl, callback) => (dispatch, getState) => { export const convertPdfToImage = (pdfUrl, callback) => (dispatch, getState) => {
// 로딩 시작 dispatch({
dispatch(changeAppStatus({ showLoadingPanel: { show: true } })); type: types.CONVERT_PDF_TO_IMAGE,
payload: pdfUrl,
});
const onSuccess = (response) => { const onSuccess = (response) => {
const blob = new Blob([response.data], { type: "image/png;charset=UTF-8" }); // retCode 체크 (프로젝트 API 규약: 200이어도 retCode로 성공/실패 구분)
const imageUrl = URL.createObjectURL(blob); const retCode = response.headers?.retcode || response.headers?.retCode;
console.log("convertPdfToImage onSuccess", response.data);
console.log("imageUrl onSuccess", imageUrl);
// const imageData = get("data.data", imageUrl);
dispatch({ if (retCode !== undefined && retCode !== 0 && retCode !== '0') {
type: types.CONVERT_PDF_TO_IMAGE_SUCCESS, const error = new Error(`API Error: retCode=${retCode}`);
// payload: imageData, dispatch({
payload: imageUrl, type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
}); payload: { pdfUrl, error },
});
callback && callback(error, null);
return;
}
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); let imageUrl;
try {
if (response.data instanceof Blob) {
if (response.data.size < 100) {
throw new Error('Invalid image data (size too small)');
}
imageUrl = URL.createObjectURL(response.data);
} else if (response.data instanceof ArrayBuffer) {
if (response.data.byteLength < 100) {
throw new Error('Invalid image data (size too small)');
}
const blob = new Blob([response.data], { type: 'image/png' });
imageUrl = URL.createObjectURL(blob);
} else {
const blob = new Blob([response.data], { type: 'image/png' });
imageUrl = URL.createObjectURL(blob);
}
// 콜백이 있으면 변환된 이미지 데이터와 함께 호출 dispatch({
// callback && callback(null, imageData); type: types.CONVERT_PDF_TO_IMAGE_SUCCESS,
callback && callback(null, imageUrl); payload: { pdfUrl, imageUrl },
});
callback && callback(null, imageUrl);
} catch (error) {
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
payload: { pdfUrl, error },
});
callback && callback(error, null);
}
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("convertPdfToImage onFail", error);
dispatch({ dispatch({
type: types.CONVERT_PDF_TO_IMAGE_FAILURE, type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
payload: error, payload: { pdfUrl, error },
}); });
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
// 콜백이 있으면 에러와 함께 호출
callback && callback(error, null); callback && callback(error, null);
}; };
// API 요청 파라미터
const params = {
pdfUrl: pdfUrl,
};
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"post", 'post',
URLS.CONVERT_IMG, URLS.CONVERT_IMG,
{}, {},
params, { pdfUrl },
onSuccess, onSuccess,
onFail onFail,
false,
'blob'
); );
}; };
/** /**
* 변환된 이미지 데이터를 초기화하는 액션 * 여러 PDF를 순차적으로 변환 (백그라운드)
* @param {Array<string>} pdfUrls - 변환할 PDF URL 배열
* @param {function} callback - 완료 후 실행할 콜백 (errors, results)
*/
export const convertMultiplePdfs = (pdfUrls, callback) => async (dispatch, getState) => {
if (!pdfUrls || pdfUrls.length === 0) {
callback && callback(null, []);
return;
}
const results = [];
const errors = [];
for (let i = 0; i < pdfUrls.length; i++) {
const pdfUrl = pdfUrls[i];
await new Promise((resolve) => {
dispatch(
convertPdfToImage(pdfUrl, (error, imageUrl) => {
if (error) {
errors.push({ pdfUrl, error });
} else {
results.push({ pdfUrl, imageUrl });
}
resolve();
})
);
});
if (i < pdfUrls.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
callback && callback(errors.length > 0 ? errors : null, results);
};
/**
* 변환된 이미지 데이터 초기화 (전체)
*/ */
export const clearConvertedImage = () => ({ export const clearConvertedImage = () => ({
type: types.CLEAR_CONVERTED_IMAGE, type: types.CLEAR_CONVERTED_IMAGE,
}); });
/**
* 특정 PDF의 변환된 이미지 데이터 제거
* @param {string} pdfUrl - 제거할 PDF URL
*/
export const clearConvertedImageByUrl = (pdfUrl) => ({
type: types.CLEAR_CONVERTED_IMAGE_BY_URL,
payload: pdfUrl,
});

View File

@@ -14,31 +14,22 @@ import {
setSystemTermination, setSystemTermination,
showError, showError,
} from '../actions/commonActions'; } from '../actions/commonActions';
import { import { getAuthenticationCode, getReAuthenticationCode } from '../actions/deviceActions';
getAuthenticationCode, import { pushPanel, resetPanels } from '../actions/panelActions';
getReAuthenticationCode,
} from '../actions/deviceActions';
import {
pushPanel,
resetPanels,
} from '../actions/panelActions';
import * as Config from '../utils/Config'; import * as Config from '../utils/Config';
import { ACTIVE_POPUP } from '../utils/Config'; import { ACTIVE_POPUP } from '../utils/Config';
import * as HelperMethods from '../utils/helperMethods'; import * as HelperMethods from '../utils/helperMethods';
import { import { getUrl, URLS } from './apiConfig';
getUrl,
URLS,
} from './apiConfig';
let tokenRefreshing = false; let tokenRefreshing = false;
const axiosQueue = []; const axiosQueue = [];
export const setTokenRefreshing = (value) => { export const setTokenRefreshing = (value) => {
console.log("TAxios setTokenRefreshing ", value); console.log('TAxios setTokenRefreshing ', value);
tokenRefreshing = value; tokenRefreshing = value;
}; };
export const runDelayedAction = (dispatch, getState) => { export const runDelayedAction = (dispatch, getState) => {
console.log("runDelayedAction axiosQueue size", axiosQueue.length); console.log('runDelayedAction axiosQueue size', axiosQueue.length);
while (axiosQueue.length > 0) { while (axiosQueue.length > 0) {
const requestConfig = axiosQueue.shift(); // queue에서 요청을 하나씩 shift const requestConfig = axiosQueue.shift(); // queue에서 요청을 하나씩 shift
TAxios( TAxios(
@@ -49,7 +40,9 @@ export const runDelayedAction = (dispatch, getState) => {
requestConfig.urlParams, requestConfig.urlParams,
requestConfig.params, requestConfig.params,
requestConfig.onSuccess, requestConfig.onSuccess,
requestConfig.onFail requestConfig.onFail,
false, // noTokenRefresh
requestConfig.responseType // ⭐ responseType 전달
); );
} }
}; };
@@ -62,23 +55,24 @@ export const TAxios = (
params = {}, params = {},
onSuccess, onSuccess,
onFail, onFail,
noTokenRefresh = false noTokenRefresh = false,
responseType = undefined // ⭐ 선택적 파라미터 추가 (기존 코드 영향 없음)
) => { ) => {
const pushQueue = () => { const pushQueue = () => {
if (!noTokenRefresh) { if (!noTokenRefresh) {
axiosQueue.push({ type, baseUrl, urlParams, params, onSuccess, onFail }); axiosQueue.push({ type, baseUrl, urlParams, params, onSuccess, onFail, responseType });
} }
}; };
const decodeMessage = (apiSysMessage) => { const decodeMessage = (apiSysMessage) => {
try { try {
const decodedBase64 = atob(apiSysMessage); const decodedBase64 = atob(apiSysMessage);
const decodedText = new TextDecoder("utf-8").decode( const decodedText = new TextDecoder('utf-8').decode(
new Uint8Array(decodedBase64.split("").map((c) => c.charCodeAt(0))) new Uint8Array(decodedBase64.split('').map((c) => c.charCodeAt(0)))
); );
return decodedText; return decodedText;
} catch (error) { } catch (error) {
console.error("Decoding error:", error); console.error('Decoding error:', error);
return apiSysMessage; return apiSysMessage;
} }
}; };
@@ -90,31 +84,34 @@ export const TAxios = (
const AUTHORIZATION = { headers: { ...httpHeader } }; const AUTHORIZATION = { headers: { ...httpHeader } };
if (accessToken) { if (accessToken) {
AUTHORIZATION.headers["lgsp_auth"] = accessToken; AUTHORIZATION.headers['lgsp_auth'] = accessToken;
} }
AUTHORIZATION.headers["dvc_id"] = deviceId; AUTHORIZATION.headers['dvc_id'] = deviceId;
AUTHORIZATION.headers["refresh_token"] = refreshToken; AUTHORIZATION.headers['refresh_token'] = refreshToken;
if (typeof window === "object") { // ⭐ responseType 옵션 추가 (이미지, PDF 등 바이너리 데이터용)
let url = Array.isArray(baseUrl) if (responseType) {
? getUrl(getState, baseUrl[0]) AUTHORIZATION.responseType = responseType;
: getUrl(getState, baseUrl); }
if (typeof window === 'object') {
let url = Array.isArray(baseUrl) ? getUrl(getState, baseUrl[0]) : getUrl(getState, baseUrl);
if (!url) { if (!url) {
//todo error page //todo error page
return; return;
} }
if (type === "get") { if (type === 'get') {
const _urlparams = HelperMethods.createQueryString(urlParams); const _urlparams = HelperMethods.createQueryString(urlParams);
url += _urlparams ? `?${_urlparams}` : ""; url += _urlparams ? `?${_urlparams}` : '';
} }
let axiosInstance; let axiosInstance;
switch (type) { switch (type) {
case "get": case 'get':
axiosInstance = axios.get(url, AUTHORIZATION); axiosInstance = axios.get(url, AUTHORIZATION);
break; break;
case "post": case 'post':
axiosInstance = axios.post(url, params, AUTHORIZATION); axiosInstance = axios.post(url, params, AUTHORIZATION);
break; break;
// TODO: 다른 HTTP 메소드 있다면 처리 (chw) // TODO: 다른 HTTP 메소드 있다면 처리 (chw)
@@ -123,18 +120,17 @@ export const TAxios = (
if (axiosInstance) { if (axiosInstance) {
axiosInstance axiosInstance
.then((res) => { .then((res) => {
console.log("TAxios response", url, res); console.log('TAxios response', url, res);
const apiSysStatus = res.headers["api-sys-status"]; const apiSysStatus = res.headers['api-sys-status'];
const apiSysMessage = res.headers["api-sys-message"]; const apiSysMessage = res.headers['api-sys-message'];
const { systemNotice, systemTermination, appStatus } = const { systemNotice, systemTermination, appStatus } = getState().common;
getState().common;
const isInitialLoad = !appStatus.loadingComplete; const isInitialLoad = !appStatus.loadingComplete;
if (apiSysStatus === "800" && !systemNotice) { if (apiSysStatus === '800' && !systemNotice) {
dispatch(setSystemNotice()); dispatch(setSystemNotice());
} else if (apiSysStatus === "900" && !systemTermination) { } else if (apiSysStatus === '900' && !systemTermination) {
const decodedMessage = decodeMessage(apiSysMessage); const decodedMessage = decodeMessage(apiSysMessage);
dispatch(setSystemTermination(isInitialLoad)); dispatch(setSystemTermination(isInitialLoad));
@@ -147,7 +143,7 @@ export const TAxios = (
}, },
}) })
); );
} else if (apiSysStatus === "901" && !systemTermination) { } else if (apiSysStatus === '901' && !systemTermination) {
const decodedMessage = decodeMessage(apiSysMessage); const decodedMessage = decodeMessage(apiSysMessage);
dispatch(setSystemTermination(isInitialLoad)); dispatch(setSystemTermination(isInitialLoad));
@@ -164,14 +160,9 @@ export const TAxios = (
if (baseUrl === URLS.GET_AUTHENTICATION_CODE) { if (baseUrl === URLS.GET_AUTHENTICATION_CODE) {
if (res?.data?.retCode !== 0) { if (res?.data?.retCode !== 0) {
console.error("accessToken failed", res.data.retCode); console.error('accessToken failed', res.data.retCode);
dispatch( dispatch(
showError( showError(res.data.retCode, res.data.retMsg, false, res.data.retDetailCode)
res.data.retCode,
res.data.retMsg,
false,
res.data.retDetailCode
)
); );
return; return;
} }
@@ -234,7 +225,7 @@ export const TAxios = (
if (onSuccess) onSuccess(res); if (onSuccess) onSuccess(res);
}) })
.catch((error) => { .catch((error) => {
console.error("TAxios ", url, error); console.error('TAxios ', url, error);
if (onFail) onFail(error); if (onFail) onFail(error);
}); });
} }
@@ -276,7 +267,7 @@ export const TAxiosPromise = (
success: true, success: true,
data: response.data, data: response.data,
response: response, response: response,
error: null error: null,
}); });
}, },
// onFail - 에러도 resolve로 처리하여 throw 방지 // onFail - 에러도 resolve로 처리하여 throw 방지
@@ -286,7 +277,7 @@ export const TAxiosPromise = (
success: false, success: false,
data: null, data: null,
response: null, response: null,
error: error error: error,
}); });
}, },
noTokenRefresh noTokenRefresh
@@ -329,7 +320,7 @@ export const TAxiosAdvancedPromise = (
success: false, success: false,
data: null, data: null,
response: null, response: null,
error: timeoutError error: timeoutError,
}); });
} }
}, timeout); }, timeout);
@@ -349,14 +340,14 @@ export const TAxiosAdvancedPromise = (
success: true, success: true,
data: response.data, data: response.data,
response: response, response: response,
error: null error: null,
}); });
}, },
// onFail // onFail
(error) => { (error) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
console.error(`TAxiosPromise error on attempt ${attempts} for ${baseUrl}:`, error); console.error(`TAxiosPromise error on attempt ${attempts} for ${baseUrl}:`, error);
// 재시도 로직 // 재시도 로직
if (attempts < maxAttempts) { if (attempts < maxAttempts) {
console.log(`Retrying in ${retryDelay}ms... (${attempts}/${maxAttempts})`); console.log(`Retrying in ${retryDelay}ms... (${attempts}/${maxAttempts})`);
@@ -372,7 +363,7 @@ export const TAxiosAdvancedPromise = (
success: false, success: false,
data: null, data: null,
response: null, response: null,
error: error error: error,
}); });
} }
} }
@@ -387,22 +378,38 @@ export const TAxiosAdvancedPromise = (
// HTTP 메소드별 편의 함수들 (안전한 버전) // HTTP 메소드별 편의 함수들 (안전한 버전)
export const TAxiosGet = async (dispatch, getState, baseUrl, urlParams = {}, options = {}) => { export const TAxiosGet = async (dispatch, getState, baseUrl, urlParams = {}, options = {}) => {
return await TAxiosPromise(dispatch, getState, 'get', baseUrl, urlParams, {}, options.noTokenRefresh); return await TAxiosPromise(
dispatch,
getState,
'get',
baseUrl,
urlParams,
{},
options.noTokenRefresh
);
}; };
export const TAxiosPost = async (dispatch, getState, baseUrl, params = {}, options = {}) => { export const TAxiosPost = async (dispatch, getState, baseUrl, params = {}, options = {}) => {
return await TAxiosPromise(dispatch, getState, 'post', baseUrl, {}, params, options.noTokenRefresh); return await TAxiosPromise(
dispatch,
getState,
'post',
baseUrl,
{},
params,
options.noTokenRefresh
);
}; };
// 안전한 다중 요청 처리 // 안전한 다중 요청 처리
export const TAxiosAll = async (requests) => { export const TAxiosAll = async (requests) => {
try { try {
const results = await Promise.all(requests); const results = await Promise.all(requests);
// 모든 결과를 안전하게 처리 // 모든 결과를 안전하게 처리
const successResults = []; const successResults = [];
const failedResults = []; const failedResults = [];
results.forEach((result, index) => { results.forEach((result, index) => {
if (result.success) { if (result.success) {
successResults.push({ index, result: result.data }); successResults.push({ index, result: result.data });
@@ -410,12 +417,12 @@ export const TAxiosAll = async (requests) => {
failedResults.push({ index, error: result.error }); failedResults.push({ index, error: result.error });
} }
}); });
return { return {
success: failedResults.length === 0, success: failedResults.length === 0,
successResults, successResults,
failedResults, failedResults,
allResults: results allResults: results,
}; };
} catch (error) { } catch (error) {
console.error('TAxiosAll unexpected error:', error); console.error('TAxiosAll unexpected error:', error);
@@ -423,7 +430,7 @@ export const TAxiosAll = async (requests) => {
success: false, success: false,
successResults: [], successResults: [],
failedResults: [{ index: -1, error }], failedResults: [{ index: -1, error }],
allResults: [] allResults: [],
}; };
} }
}; };
@@ -432,7 +439,7 @@ export const TAxiosAll = async (requests) => {
export const TAxiosSequential = async (requests) => { export const TAxiosSequential = async (requests) => {
const results = []; const results = [];
const errors = []; const errors = [];
for (let i = 0; i < requests.length; i++) { for (let i = 0; i < requests.length; i++) {
try { try {
const result = await requests[i]; const result = await requests[i];
@@ -447,44 +454,42 @@ export const TAxiosSequential = async (requests) => {
console.error(`TAxiosSequential unexpected error at request ${i}:`, error); console.error(`TAxiosSequential unexpected error at request ${i}:`, error);
} }
} }
return { return {
success: errors.length === 0, success: errors.length === 0,
results, results,
errors errors,
}; };
}; };
// 안전한 Redux Thunk 헬퍼 // 안전한 Redux Thunk 헬퍼
export const createSafeApiThunk = (apiCall) => { export const createSafeApiThunk = (apiCall) => {
return (...args) => async (dispatch, getState) => { return (...args) =>
try { async (dispatch, getState) => {
const result = await apiCall(dispatch, getState, ...args); try {
return result; // 이미 안전한 형태로 반환됨 const result = await apiCall(dispatch, getState, ...args);
} catch (error) { return result; // 이미 안전한 형태로 반환됨
console.error('API thunk unexpected error:', error); } catch (error) {
return { console.error('API thunk unexpected error:', error);
success: false, return {
data: null, success: false,
response: null, data: null,
error response: null,
}; error,
} };
}; }
};
}; };
// 실제 사용 예시들 (안전한 버전) // 실제 사용 예시들 (안전한 버전)
export const safeUsageExamples = { export const safeUsageExamples = {
// 1. 기본 안전한 사용법 // 1. 기본 안전한 사용법
basicSafeUsage: async (dispatch, getState) => { basicSafeUsage: async (dispatch, getState) => {
const result = await TAxiosPromise( const result = await TAxiosPromise(dispatch, getState, 'get', URLS.GET_HOME_TERMS, {
dispatch, trmsTpCdList: 'MST00401, MST00402',
getState, mbrNo: '12345',
'get', });
URLS.GET_HOME_TERMS,
{ trmsTpCdList: "MST00401, MST00402", mbrNo: "12345" }
);
if (result.success) { if (result.success) {
console.log('Success:', result.data); console.log('Success:', result.data);
return result.data; return result.data;
@@ -497,78 +502,75 @@ export const safeUsageExamples = {
// 2. retCode 체크를 포함한 안전한 처리 // 2. retCode 체크를 포함한 안전한 처리
safeWithRetCodeCheck: async (dispatch, getState) => { safeWithRetCodeCheck: async (dispatch, getState) => {
const result = await TAxiosGet( const result = await TAxiosGet(dispatch, getState, URLS.GET_HOME_TERMS, {
dispatch, trmsTpCdList: 'MST00401, MST00402',
getState, mbrNo: '12345',
URLS.GET_HOME_TERMS, });
{ trmsTpCdList: "MST00401, MST00402", mbrNo: "12345" }
);
if (!result.success) { if (!result.success) {
console.error('Network error:', result.error); console.error('Network error:', result.error);
return { success: false, message: '네트워크 오류가 발생했습니다.' }; return { success: false, message: '네트워크 오류가 발생했습니다.' };
} }
if (result.data.retCode !== 0) { if (result.data.retCode !== 0) {
console.error('API error:', result.data.retCode, result.data.retMsg); console.error('API error:', result.data.retCode, result.data.retMsg);
return { return {
success: false, success: false,
message: result.data.retMsg || 'API 오류가 발생했습니다.' message: result.data.retMsg || 'API 오류가 발생했습니다.',
}; };
} }
return { success: true, data: result.data }; return { success: true, data: result.data };
}, },
// 3. 여러 요청의 안전한 처리 // 3. 여러 요청의 안전한 처리
safeParallelRequests: async (dispatch, getState) => { safeParallelRequests: async (dispatch, getState) => {
const requests = [ const requests = [
TAxiosGet(dispatch, getState, URLS.GET_HOME_TERMS, { mbrNo: "12345" }), TAxiosGet(dispatch, getState, URLS.GET_HOME_TERMS, { mbrNo: '12345' }),
TAxiosGet(dispatch, getState, URLS.GET_USER_INFO, { mbrNo: "12345" }), TAxiosGet(dispatch, getState, URLS.GET_USER_INFO, { mbrNo: '12345' }),
TAxiosPost(dispatch, getState, URLS.UPDATE_SETTINGS, { setting: "value" }) TAxiosPost(dispatch, getState, URLS.UPDATE_SETTINGS, { setting: 'value' }),
]; ];
const result = await TAxiosAll(requests); const result = await TAxiosAll(requests);
if (result.success) { if (result.success) {
console.log('All requests succeeded'); console.log('All requests succeeded');
return result.successResults.map(item => item.result); return result.successResults.map((item) => item.result);
} else { } else {
console.error('Some requests failed:', result.failedResults); console.error('Some requests failed:', result.failedResults);
// 부분적 성공도 처리 가능 // 부분적 성공도 처리 가능
return { return {
successData: result.successResults.map(item => item.result), successData: result.successResults.map((item) => item.result),
errors: result.failedResults errors: result.failedResults,
}; };
} }
} },
}; };
// 컴포넌트에서의 안전한 사용법 // 컴포넌트에서의 안전한 사용법
export const ComponentUsageExample = () => { export const ComponentUsageExample = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const handleFetchTerms = async () => { const handleFetchTerms = async () => {
setLoading(true); setLoading(true);
setError(null); setError(null);
const result = await dispatch(fetchCurrentUserHomeTermsSafe()); const result = await dispatch(fetchCurrentUserHomeTermsSafe());
setLoading(false); setLoading(false);
if (result.success) { if (result.success) {
console.log("Terms fetched successfully"); console.log('Terms fetched successfully');
// 성공 처리 (예: 성공 토스트 표시) // 성공 처리 (예: 성공 토스트 표시)
} else { } else {
console.error("Failed to fetch terms:", result.message); console.error('Failed to fetch terms:', result.message);
setError(result.message); setError(result.message);
// 에러 처리 (예: 에러 토스트 표시) // 에러 처리 (예: 에러 토스트 표시)
} }
}; };
return ( return (
<div> <div>
<button onClick={handleFetchTerms} disabled={loading}> <button onClick={handleFetchTerms} disabled={loading}>
@@ -577,4 +579,4 @@ export const ComponentUsageExample = () => {
{error && <div className="error-message">{error}</div>} {error && <div className="error-message">{error}</div>}
</div> </div>
); );
}; };

View File

@@ -515,3 +515,29 @@
} }
} }
} }
// 🔽 에너지 라벨 팝업 스타일 (1920x1080 TV 화면 기준)
.energyPopupContent {
width: 100%;
max-height: 800px; // 팝업 타이틀/버튼 영역 제외한 콘텐츠 최대 높이
overflow-y: auto;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.energyImagesContainer {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.energyImage {
max-width: 100%;
max-height: 750px; // 1080px - 타이틀(~120px) - 버튼(~120px) - 여백(~90px)
width: auto;
height: auto;
object-fit: contain; // 비율 유지하면서 컨테이너에 맞춤
}

View File

@@ -1,75 +1,62 @@
import React, { import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
memo,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { import { useDispatch, useSelector } from 'react-redux';
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import defaultLogoImg import defaultLogoImg from '../../../assets/images/ic-tab-partners-default@3x.png';
from '../../../assets/images/ic-tab-partners-default@3x.png'; import defaultimgHorizontal from '../../../assets/images/img-thumb-empty-hor@3x.png';
import defaultimgHorizontal import defaultImageItem from '../../../assets/images/img-thumb-empty-product@3x.png';
from '../../../assets/images/img-thumb-empty-hor@3x.png'; import defaultimgVertical from '../../../assets/images/img-thumb-empty-ver@3x.png';
import defaultImageItem
from '../../../assets/images/img-thumb-empty-product@3x.png';
import defaultimgVertical
from '../../../assets/images/img-thumb-empty-ver@3x.png';
import IcLiveShow from '../../../assets/images/tag/tag-liveshow.png'; import IcLiveShow from '../../../assets/images/tag/tag-liveshow.png';
import testPdf from '../../../assets/mock/test.pdf'; // 🧪 테스트용 에너지 라벨 (실제 PDF 변환 테스트)
import { import testEnergyIconA from '../../../assets/images/energyLabel/labelgradeA.png';
setHidePopup, import testEnergyIconB from '../../../assets/images/energyLabel/labelgradeB.png';
setShowPopup, import testEnergyIconC from '../../../assets/images/energyLabel/labelgradeC.png';
} from '../../actions/commonActions'; import testEnergyPdf from '../../../assets/mock/EnergyLabelSample.pdf';
import { setHidePopup, setShowPopup } from '../../actions/commonActions';
import { import {
clearConvertedImage, clearConvertedImage,
convertPdfToImage, convertPdfToImage,
convertMultiplePdfs,
} from '../../actions/convertActions'; } from '../../actions/convertActions';
import { sendLogTotalRecommend } from '../../actions/logActions'; import { sendLogTotalRecommend } from '../../actions/logActions';
import usePriceInfo from '../../hooks/usePriceInfo'; import usePriceInfo from '../../hooks/usePriceInfo';
import * as Config from '../../utils/Config'; import * as Config from '../../utils/Config';
import { import { $L, getQRCodeUrl, removeSpecificTags } from '../../utils/helperMethods';
$L, import { SpotlightIds } from '../../utils/SpotlightIds';
getQRCodeUrl,
removeSpecificTags,
} from '../../utils/helperMethods';
import CustomImage from '../CustomImage/CustomImage'; import CustomImage from '../CustomImage/CustomImage';
import TPopUp from '../TPopUp/TPopUp'; import TPopUp from '../TPopUp/TPopUp';
import css from './TItemCard.module.less'; import css from './TItemCard.module.less';
const SpottableComponent = Spottable("div"); const SpottableComponent = Spottable('div');
const SpottableTemp = Spottable("div"); const SpottableTemp = Spottable('div');
const TYPES = { const TYPES = {
vertical: "vertical", vertical: 'vertical',
horizontal: "horizontal", horizontal: 'horizontal',
videoShow: "videoShow", videoShow: 'videoShow',
}; };
const IMAGETYPES = { const IMAGETYPES = {
imgHorizontal: "imgHorizontal", imgHorizontal: 'imgHorizontal',
imgVertical: "imgVertical", imgVertical: 'imgVertical',
}; };
const STRING_CONF = { const STRING_CONF = {
SOLD_OUT: "SOLD OUT", SOLD_OUT: 'SOLD OUT',
ENERGY_LOADING: "Loading energy label...", ENERGY_LOADING: 'Loading energy label...',
ENERGY_ERROR: "Failed to load energy label", ENERGY_ERROR: 'Failed to load energy label',
}; };
export const removeDotAndColon = (string) => { export const removeDotAndColon = (string) => {
return /[.:]/.test(string) ? string.replace(/[.:]/g, "") : string; return /[.:]/.test(string) ? string.replace(/[.:]/g, '') : string;
}; };
const parsePrice = (price) => { const parsePrice = (price) => {
return parseFloat(price?.replace(/[^0-9.-]+/g, "") || "0"); return parseFloat(price?.replace(/[^0-9.-]+/g, '') || '0');
}; };
export default memo(function TItemCardNew({ export default memo(function TItemCardNew({
@@ -126,25 +113,28 @@ export default memo(function TItemCardNew({
const [currentPdfUrl, setCurrentPdfUrl] = useState(null); const [currentPdfUrl, setCurrentPdfUrl] = useState(null);
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd); const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
const cursorVisible = useSelector( const cursorVisible = useSelector((state) => state.common.appStatus.cursorVisible);
(state) => state.common.appStatus.cursorVisible const { activePopup, popupVisible } = useSelector((state) => state.common.popup);
);
const { activePopup, popupVisible } = useSelector(
(state) => state.common.popup
);
const convert = useSelector((state) => state.convert); const convert = useSelector((state) => state.convert);
const serverHOST = useSelector((state) => state.common.appStatus.serverHOST); const serverHOST = useSelector((state) => state.common.appStatus.serverHOST);
const serverType = useSelector((state) => state.localSettings.serverType); const serverType = useSelector((state) => state.localSettings.serverType);
// 컴포넌트 unmount 시 메모리 정리
useEffect(() => {
return () => {
if (convert?.convertedImage) {
URL.revokeObjectURL(convert.convertedImage);
}
};
}, [convert?.convertedImage]);
useEffect(() => { useEffect(() => {
if (!imageSource) { if (!imageSource) {
if (type === "videoShow") { if (type === 'videoShow') {
setDefaultImage( setDefaultImage(
imgType === IMAGETYPES.imgHorizontal imgType === IMAGETYPES.imgHorizontal ? defaultimgHorizontal : defaultimgVertical
? defaultimgHorizontal
: defaultimgVertical
); );
} else { } else {
setDefaultImage(defaultImageItem); setDefaultImage(defaultImageItem);
@@ -152,8 +142,27 @@ export default memo(function TItemCardNew({
} }
}, [imageSource, type, imgType]); }, [imageSource, type, imgType]);
const { originalPrice, discountedPrice, discountRate } = // ⚠️ 자동 변환 비활성화 (클릭 시에만 변환 테스트)
usePriceInfo(priceInfo) || {}; // useEffect(() => {
// if (euEnrgLblInfos && euEnrgLblInfos.length > 0) {
// const pdfUrls = euEnrgLblInfos
// .filter((info) => info?.enrgLblUrl && !info.enrgLblUrl.endsWith('.png'))
// .map((info) => info.enrgLblUrl);
//
// if (pdfUrls.length > 0) {
// console.log(`🔄 [EnergyLabel] Auto-converting ${pdfUrls.length} PDFs for product:`, productId);
// dispatch(convertMultiplePdfs(pdfUrls, (errors, results) => {
// if (errors) {
// console.error(`❌ [EnergyLabel] Some conversions failed for product:`, productId, errors);
// } else {
// console.log(`✅ [EnergyLabel] All conversions successful for product:`, productId);
// }
// }));
// }
// }
// }, [euEnrgLblInfos, productId, dispatch]);
const { originalPrice, discountedPrice, discountRate } = usePriceInfo(priceInfo) || {};
const _onBlur = useCallback(() => { const _onBlur = useCallback(() => {
if (onBlur) { if (onBlur) {
@@ -241,25 +250,23 @@ export default memo(function TItemCardNew({
); );
const ariaLabel = useMemo(() => { const ariaLabel = useMemo(() => {
const soldOutText = soldoutFlag === "Y" ? "Sold Out " : ""; const soldOutText = soldoutFlag === 'Y' ? 'Sold Out ' : '';
const firstLabelText = firstLabel ? `${firstLabel} ` : ""; const firstLabelText = firstLabel ? `${firstLabel} ` : '';
const discountLabel = discountRate ? `${discountRate} discount, ` : ""; const discountLabel = discountRate ? `${discountRate} discount, ` : '';
const discountPriceLabel = discountRate const discountPriceLabel = discountRate ? `Sale price ${discountedPrice}, ` : '';
? `Sale price ${discountedPrice}, `
: "";
const parsedPrice = parsePrice(originalPrice); const parsedPrice = parsePrice(originalPrice);
const priceLabel = const priceLabel =
parsedPrice === 0 parsedPrice === 0
? offerInfo ? offerInfo
? ` ${offerInfo}` ? ` ${offerInfo}`
: "" : ''
: originalPrice : originalPrice
? ` Original price ${originalPrice}, ` ? ` Original price ${originalPrice}, `
: ""; : '';
const productLabel = label || ""; const productLabel = label || '';
const lastLabelText = lastLabel || ""; const lastLabelText = lastLabel || '';
return `${soldOutText}${firstLabelText}${discountLabel}${productName}${discountPriceLabel}${priceLabel}${productLabel}${lastLabelText}`; return `${soldOutText}${firstLabelText}${discountLabel}${productName}${discountPriceLabel}${priceLabel}${productLabel}${lastLabelText}`;
}, [ }, [
@@ -279,28 +286,50 @@ export default memo(function TItemCardNew({
return sanitizedString; return sanitizedString;
}, [productName]); }, [productName]);
// 🔽 팝업 닫기 // 🔽 팝업 닫기 + 메모리 정리
const handleClosePopup = useCallback(() => { const handleClosePopup = useCallback(() => {
// Object URL 메모리 해제
if (convert?.convertedImage) {
URL.revokeObjectURL(convert.convertedImage);
}
dispatch(setHidePopup()); dispatch(setHidePopup());
dispatch(clearConvertedImage()); dispatch(clearConvertedImage());
setCurrentPdfUrl(null); setCurrentPdfUrl(null);
}, [dispatch]); }, [dispatch, convert?.convertedImage]);
const { setupPinUrl } = useMemo(() => { const { setupPinUrl } = useMemo(() => {
return getQRCodeUrl({ serverHOST, serverType }); return getQRCodeUrl({ serverHOST, serverType });
}, [serverHOST, serverType]); }, [serverHOST, serverType]);
// 🔽 에너지 라벨 클릭 - PDF 변환 시작
const onEnergyClick = useCallback( const onEnergyClick = useCallback(
(e, pdfUrl) => { (e, pdfUrl) => {
e.stopPropagation(); e.stopPropagation();
setCurrentPdfUrl(pdfUrl); setCurrentPdfUrl(pdfUrl);
dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup));
// PDF를 이미지로 변환 // PNG 이미지는 직접 표시
// dispatch(convertPdfToImage(pdfUrl)); if (pdfUrl.endsWith('.png')) {
dispatch(convertPdfToImage(testPdf)); dispatch({
type: 'CONVERT_PDF_TO_IMAGE_SUCCESS',
payload: { pdfUrl, imageUrl: pdfUrl },
});
dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup));
return;
}
// PDF 변환 시작 (성공 시에만 팝업)
dispatch(
convertPdfToImage(pdfUrl, (error, imageUrl) => {
if (error) {
console.error('[EnergyLabel] 변환 실패:', error.message || error);
} else {
dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup));
setTimeout(() => {
Spotlight.focus(SpotlightIds.TPOPUP);
}, 250);
}
})
);
}, },
[dispatch] [dispatch]
); );
@@ -311,15 +340,13 @@ export default memo(function TItemCardNew({
className={classNames( className={classNames(
css[type], css[type],
nonPosition && css.nonPosition, nonPosition && css.nonPosition,
type === "videoShow" && css[imgType], type === 'videoShow' && css[imgType],
className && className className && className
)} )}
onBlur={_onBlur} onBlur={_onBlur}
onClick={_onClick} onClick={_onClick}
onFocus={_onFocus} onFocus={_onFocus}
spotlightId={ spotlightId={spotlightId ?? 'spotlightId-' + removeDotAndColon(productId)}
spotlightId ?? "spotlightId-" + removeDotAndColon(productId)
}
aria-label={ariaLabel} aria-label={ariaLabel}
role="button" role="button"
{...rest} {...rest}
@@ -330,7 +357,7 @@ export default memo(function TItemCardNew({
delay={0} delay={0}
src={imageSource} src={imageSource}
fallbackSrc={ fallbackSrc={
type === "videoShow" type === 'videoShow'
? imgType === IMAGETYPES.imgHorizontal ? imgType === IMAGETYPES.imgHorizontal
? defaultimgHorizontal ? defaultimgHorizontal
: defaultimgVertical : defaultimgVertical
@@ -338,24 +365,15 @@ export default memo(function TItemCardNew({
} }
onError={addDefaultImg} onError={addDefaultImg}
/> />
{priceInfo && {priceInfo && discountRate && Number(discountRate.replace('%', '')) > 4 && (
discountRate && <span className={css.discount}>{discountRate}</span>
Number(discountRate.replace("%", "")) > 4 && ( )}
<span className={css.discount}>{discountRate}</span> {soldoutFlag && soldoutFlag === 'Y' && (
)} <div className={classNames(css.soldout, countryCode === 'DE' && css.de)}>
{soldoutFlag && soldoutFlag === "Y" && (
<div
className={classNames(
css.soldout,
countryCode === "DE" && css.de
)}
>
{$L(STRING_CONF.SOLD_OUT)} {$L(STRING_CONF.SOLD_OUT)}
</div> </div>
)} )}
{isLive && ( {isLive && <img className={css.liveTag} src={IcLiveShow} alt="Live Show" />}
<img className={css.liveTag} src={IcLiveShow} alt="Live Show" />
)}
</div> </div>
<div className={css.flexBox}> <div className={css.flexBox}>
<div <div
@@ -383,17 +401,14 @@ export default memo(function TItemCardNew({
{children} {children}
{priceInfo ? ( {priceInfo ? (
<p className={css.priceInfo}> <p className={css.priceInfo}>
{parseFloat(originalPrice?.replace(/[^0-9.-]+/g, "") || "0") === {parseFloat(originalPrice?.replace(/[^0-9.-]+/g, '') || '0') === 0 ? (
0 ? (
<strong>{offerInfo}</strong> <strong>{offerInfo}</strong>
) : discountRate ? ( ) : discountRate ? (
discountedPrice discountedPrice
) : ( ) : (
originalPrice originalPrice
)} )}
{discountRate && ( {discountRate && <span className={css.originalPrice}>{originalPrice}</span>}
<span className={css.originalPrice}>{originalPrice}</span>
)}
</p> </p>
) : ( ) : (
<p className={css.offerInfo}>{offerInfo}</p> <p className={css.offerInfo}>{offerInfo}</p>
@@ -402,12 +417,68 @@ export default memo(function TItemCardNew({
{originPrice && ( {originPrice && (
<p className={css.priceInfo}> <p className={css.priceInfo}>
{dcPrice ? dcPrice : originPrice} {dcPrice ? dcPrice : originPrice}
{dcPrice && ( {dcPrice && <span className={css.originalPrice}>{originPrice}</span>}
<span className={css.originalPrice}>{originPrice}</span>
)}
</p> </p>
)} )}
</div> </div>
{/* 🧪 테스트: 모든 상품에 [EnergyLabel] 표시 (test.pdf 변환 확인용) */}
{/* ✅ 실제 운영: 아래 주석 삭제하고 원래 조건문으로 복구 */}
{(() => {
let energyLabels;
// 실제 API 데이터가 있으면 사용
if (euEnrgLblInfos?.length > 0 && euEnrgLblInfos[0]?.enrgLblIcnUrl !== null) {
energyLabels = euEnrgLblInfos;
} else {
// 🧪 테스트: 랜덤으로 1~3개 생성
// productId 전체를 해시해서 균등한 분산 (같은 상품은 항상 같은 개수)
const hashCode = (str) => {
let hash = 0;
const s = String(str);
for (let i = 0; i < s.length; i++) {
hash = (hash << 5) - hash + s.charCodeAt(i);
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash);
};
const seed = productId ? hashCode(productId) : Math.floor(Math.random() * 1000);
const randomCount = (seed % 3) + 1; // 1, 2, 3 중 하나
const testIcons = [testEnergyIconA, testEnergyIconB, testEnergyIconC];
const testGrades = ['A (TEST)', 'B (TEST)', 'C (TEST)'];
energyLabels = Array.from({ length: randomCount }, (_, index) => ({
enrgLblUrl: testEnergyPdf,
enrgLblIcnUrl: testIcons[index],
enrgGrade: testGrades[index],
}));
}
// 하나의 labelImgBox 안에 모든 라벨 배치 (세로로 쌓임)
return (
<div className={css.labelImgBox}>
{energyLabels
.filter((info, index) => index < 3)
.map((info, index) => (
<SpottableTemp
key={index}
spotlightDisabled={Boolean(!cursorVisible)}
onClick={(e) => onEnergyClick(e, info.enrgLblUrl)}
aria-label={`Energy Efficiency ${info.enrgGrade || ''}`}
>
<CustomImage
alt={`Energy Label ${info.enrgGrade || index + 1}`}
delay={0}
src={info.enrgLblIcnUrl}
/>
</SpottableTemp>
))}
</div>
);
})()}
{/*
원래 코드 (테스트 완료 후 복구):
{euEnrgLblInfos && {euEnrgLblInfos &&
euEnrgLblInfos.length > 0 && euEnrgLblInfos.length > 0 &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== null && euEnrgLblInfos[0]?.enrgLblIcnUrl !== null &&
@@ -429,6 +500,7 @@ export default memo(function TItemCardNew({
</div> </div>
) )
)} )}
*/}
</div> </div>
{isBestSeller && rank && ( {isBestSeller && rank && (
<div className={css.bestSeller}> <div className={css.bestSeller}>
@@ -437,30 +509,53 @@ export default memo(function TItemCardNew({
)} )}
</SpottableComponent> </SpottableComponent>
{activePopup === Config.ACTIVE_POPUP.energyPopup && currentPdfUrl && ( {(() => {
<TPopUp const showPopup = activePopup === Config.ACTIVE_POPUP.energyPopup && currentPdfUrl;
kind="energyPopup" if (!showPopup) return null;
title={$L("Energy Efficiency")}
hasText return (
open={popupVisible} <TPopUp
hasButton kind="energyPopup"
button1Text={$L("CLOSE")} title={$L('Energy Efficiency')}
onClose={handleClosePopup} hasText
> open={popupVisible}
<div className={css.energyPopupContent}> hasButton
{convert && ( button1Text={$L('CLOSE')}
<div className={css.energyImagesContainer}> onClose={handleClosePopup}
<img >
alt="Energy Label" <div className={css.energyPopupContent}>
// delay={0} {convert ? (
src={convert.convertedImage} <>
className={css.energyImage} <div className={css.energyImagesContainer}>
/> {convert.convertedImage ? (
</div> <img
)} alt="Energy Label"
</div> src={convert.convertedImage}
</TPopUp> className={css.energyImage}
)} />
) : convert.error ? (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p style={{ fontSize: '0.8em', marginTop: '10px' }}>
{convert.error?.message || String(convert.error)}
</p>
</div>
) : (
<div>
<p>{$L(STRING_CONF.ENERGY_LOADING)}</p>
</div>
)}
</div>
</>
) : (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
</div>
)}
</div>
</TPopUp>
);
})()}
</> </>
); );
}); });

View File

@@ -1,32 +1,75 @@
import { types } from '../actions/actionTypes'; import { types } from '../actions/actionTypes';
import { import { curry, get } from '../utils/fp';
curry,
get,
set,
} from '../utils/fp';
// 초기 상태 정의
const initialState = { const initialState = {
convertedImage: null, convertedImage: null,
convertedImages: {},
error: null, error: null,
errors: {},
isConverting: false, isConverting: false,
converting: {},
}; };
// 변환 성공 핸들러 const convertSuccess = curry((state, action) => {
const convertSuccess = curry((state, action) => const payload = get('payload', action);
set( let imageUrl;
"convertedImage", let pdfUrl;
get("payload", action),
set("error", null, set("isConverting", false, state))
)
);
// 변환 실패 핸들러 if (typeof payload === 'string') {
const convertFailure = curry((state, action) => imageUrl = payload;
set("error", get("payload", action), set("isConverting", false, state)) } else if (payload && typeof payload === 'object') {
); pdfUrl = payload.pdfUrl;
imageUrl = payload.imageUrl;
} else {
return state;
}
return {
...state,
convertedImage: imageUrl,
convertedImages: pdfUrl
? {
...state.convertedImages,
[pdfUrl]: imageUrl,
}
: state.convertedImages,
error: null,
isConverting: false,
};
});
const convertStart = curry((state, action) => {
const pdfUrl = get('payload', action);
return {
...state,
isConverting: true,
converting: {
...state.converting,
[pdfUrl]: true,
},
};
});
const convertFailure = curry((state, action) => {
const payload = get('payload', action);
const { pdfUrl, error } = payload;
return {
...state,
error: error,
errors: {
...state.errors,
[pdfUrl]: error,
},
isConverting: false,
converting: {
...state.converting,
[pdfUrl]: false,
},
};
});
// 변환 데이터 초기화 핸들러
const clearConverted = curry((state) => ({ const clearConverted = curry((state) => ({
...state, ...state,
convertedImage: null, convertedImage: null,
@@ -34,15 +77,30 @@ const clearConverted = curry((state) => ({
isConverting: false, isConverting: false,
})); }));
// 액션 타입별 핸들러 매핑 const clearConvertedByUrl = curry((state, action) => {
const pdfUrl = get('payload', action);
const { [pdfUrl]: removedImage, ...restImages } = state.convertedImages;
const { [pdfUrl]: removedError, ...restErrors } = state.errors;
const { [pdfUrl]: removedConverting, ...restConverting } = state.converting;
return {
...state,
convertedImages: restImages,
errors: restErrors,
converting: restConverting,
};
});
const handlers = { const handlers = {
[types.CONVERT_PDF_TO_IMAGE]: convertStart,
[types.CONVERT_PDF_TO_IMAGE_SUCCESS]: convertSuccess, [types.CONVERT_PDF_TO_IMAGE_SUCCESS]: convertSuccess,
[types.CONVERT_PDF_TO_IMAGE_FAILURE]: convertFailure, [types.CONVERT_PDF_TO_IMAGE_FAILURE]: convertFailure,
[types.CLEAR_CONVERTED_IMAGE]: clearConverted, [types.CLEAR_CONVERTED_IMAGE]: clearConverted,
[types.CLEAR_CONVERTED_IMAGE_BY_URL]: clearConvertedByUrl,
}; };
export const convertReducer = (state = initialState, action = {}) => { export const convertReducer = (state = initialState, action = {}) => {
const type = get("type", action); const type = get('type', action);
const handler = handlers[type]; const handler = handlers[type];
return handler ? handler(state, action) : state; return handler ? handler(state, action) : state;
}; };