diff --git a/package.json b/package.json index 0b18402..9fbffb8 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "react-router": "^7.8.2", "react-router-dom": "^7.8.2", "react-slidedown": "^2.4.7", + "react-spinners": "^0.17.0", "react-tabs": "^6.1.0", "react-toastify": "^11.0.5", "react-tooltip": "^5.29.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a09a5ce..a64cabc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,6 +209,9 @@ importers: react-slidedown: specifier: ^2.4.7 version: 2.4.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-spinners: + specifier: ^0.17.0 + version: 0.17.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-tabs: specifier: ^6.1.0 version: 6.1.0(react@19.1.1) @@ -4829,6 +4832,12 @@ packages: react: ^16.3.0 || 17 react-dom: ^16.3.0 || 17 + react-spinners@0.17.0: + resolution: {integrity: sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-tabs@6.1.0: resolution: {integrity: sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==} peerDependencies: @@ -11218,6 +11227,11 @@ snapshots: react-dom: 19.1.1(react@19.1.1) tslib: 2.8.1 + react-spinners@0.17.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-tabs@6.1.0(react@19.1.1): dependencies: clsx: 2.1.1 diff --git a/src/entities/common/ui/cash-receipt-sample.tsx b/src/entities/common/ui/cash-receipt-sample.tsx index 851e32a..0cba221 100644 --- a/src/entities/common/ui/cash-receipt-sample.tsx +++ b/src/entities/common/ui/cash-receipt-sample.tsx @@ -3,11 +3,20 @@ import { snackBar, appBridge } from '@/shared/lib'; import { toPng } from 'html-to-image'; import { useTranslation } from 'react-i18next'; import '@/shared/ui/assets/css/style-tax-invoice.css'; -import { useEffect } from 'react'; +import { useEffect, useState, CSSProperties } from 'react'; +import { ClipLoader, FadeLoader } from 'react-spinners'; import { NumericFormat } from 'react-number-format'; import { AmountInfo, CustomerInfo, IssueInfo, MerchantInfo, ProductInfo, TransactionInfo } from '@/entities/transaction/model/types'; import moment from 'moment'; +const override: CSSProperties = { + position: 'fixed', + display: 'block', + margin: '0 auto', + top: '50%', + left: '50%', +}; + export interface CashReceiptSampleProps { cashReceiptSampleOn: boolean; setCashReceiptSampleOn: (cashReceiptSampleOn: boolean) => void; @@ -29,6 +38,9 @@ export const CashReceiptSample = ({ customerInfo, productInfo }: CashReceiptSampleProps) => { + let [loading, setLoading] = useState(false); + let [color, setColor] = useState('#0b0606'); + const { t } = useTranslation(); const downloadImage = async () => { @@ -36,22 +48,24 @@ export const CashReceiptSample = ({ try { const imageDataUrl = await toPng(section); + const fileName = 'cash-receipt-' + moment().format('YYMMDDHHmmss'); // iOS 네이티브 환경인 경우 네이티브 브릿지 사용 if (appBridge.isIOS()) { try { // data:image/png;base64, 부분 제거하고 순수 base64 데이터만 추출 const base64Data = imageDataUrl.split(',')[1]; - const fileName = `cash_receipt_${moment().format('YYYYMMDDHHmmss')}.png`; const result = await appBridge.saveImage({ imageData: base64Data, - fileName: fileName, + fileName: fileName + '.png', mimeType: 'image/png' }); if (result.success) { - snackBar(t('common.imageSaved')); + snackBar(t('common.imageSaved'), function(){ + onClickToClose(); + }); } else { throw new Error(result.error || 'Failed to save image'); } @@ -62,14 +76,18 @@ export const CashReceiptSample = ({ } else { // Android 또는 웹 환경인 경우 기존 방식 사용 (다운로드 링크) const link = document.createElement('a'); - link.download = `cash_receipt_${moment().format('YYYYMMDDHHmmss')}.png`; - link.href = imageDataUrl; + link.download = fileName + '.png'; + link.href = imageDataUrl + '#' + fileName + '.png'; link.click(); - snackBar(t('common.imageRequested')); + snackBar(t('common.imageRequested'), function(){ + onClickToClose(); + }); } } catch (error) { console.error('Image generation failed:', error); snackBar(t('common.imageGenerationFailed')); + } finally { + setLoading(false); } }; const onClickToClose = () => { @@ -95,6 +113,7 @@ export const CashReceiptSample = ({ useEffect(() => { if(!!cashReceiptSampleOn){ + setLoading(true); setTimeout(() => { downloadImage(); }, 300); @@ -270,6 +289,21 @@ export const CashReceiptSample = ({ + { !!loading && + <> +
+ + + } ); }; \ No newline at end of file diff --git a/src/entities/common/ui/deposit-receipt-sample.tsx b/src/entities/common/ui/deposit-receipt-sample.tsx index b1d6c9b..d9d1ef6 100644 --- a/src/entities/common/ui/deposit-receipt-sample.tsx +++ b/src/entities/common/ui/deposit-receipt-sample.tsx @@ -3,119 +3,152 @@ import { snackBar } from '@/shared/lib'; import { toPng } from 'html-to-image'; import { useTranslation } from 'react-i18next'; import '@/shared/ui/assets/css/style-tax-invoice.css'; -import { useEffect } from 'react'; +import { ClipLoader, FadeLoader } from 'react-spinners'; +import { CSSProperties, useEffect, useState } from 'react'; import { NumericFormat } from 'react-number-format'; import { DepositInfo } from '@/entities/transaction/model/types'; +import moment from 'moment'; export interface DepositReceiptSampleProps { - depositReceiptSampleOn: boolean; - setDepositReceiptSampleOn: (DepositReceiptSampleOn: boolean) => void; - depositInfo?: DepositInfo + depositReceiptSampleOn: boolean; + setDepositReceiptSampleOn: (DepositReceiptSampleOn: boolean) => void; + depositInfo?: DepositInfo }; +const override: CSSProperties = { + position: 'fixed', + display: 'block', + margin: '0 auto', + top: '50%', + left: '50%', + zIndex: 2000 +}; + + export const DepositReceiptSample = ({ - depositReceiptSampleOn, - setDepositReceiptSampleOn, - depositInfo + depositReceiptSampleOn, + setDepositReceiptSampleOn, + depositInfo }: DepositReceiptSampleProps) => { - const { t } = useTranslation(); + const { t } = useTranslation(); + let [loading, setLoading] = useState(false); + let [color, setColor] = useState('#0b0606'); - const downloadImage = () => { - const section = document.getElementById('image-section') as HTMLElement - toPng(section).then((image) => { - const link = document.createElement('a'); - link.download = 'downloadImage.png'; - link.href = image; - link.click(); - snackBar(t('common.imageRequested'), () => { - onClickToClose(); - }); - }); - } - const onClickToClose = () => { - setDepositReceiptSampleOn(false); - }; + const downloadImage = () => { + const section = document.getElementById('image-section') as HTMLElement + toPng(section).then((image) => { + const link = document.createElement('a'); + let fileName = 'receipt-confirmation-' + moment().format('YYMMDDHHmmss'); + link.download = fileName + '.png'; + link.href = image + '#' + fileName + '.png'; + link.click(); + snackBar(t('common.imageRequested'), () => { + onClickToClose(); + }); + }).finally(() => { + setLoading(false); + }); + } + const onClickToClose = () => { + setDepositReceiptSampleOn(false); + }; - useEffect(() => { - if(!!depositReceiptSampleOn){ - setTimeout(() => { - downloadImage(); - }, 300); - } - }, [depositReceiptSampleOn]); - - return ( - <> -
-
-
-
-
- NICEPAY -
-
-
-
입금확인증
-
-
-
-
-
- - - - - - - -
-
입금정보
-
-
-
입금일시
-
{ depositInfo?.depositDate}
-
-
-
입금은행
-
{ depositInfo?.depositBank }
-
-
-
입금계좌
-
{ depositInfo?.depositAccount }
-
-
-
금액
-
- -
-
-
-
입금적요
-
{ depositInfo?.depositReason }
-
-
-
입금ID
-
{ depositInfo?.depositId }
-
-
-
-
-
- - ); + useEffect(() => { + if (!!depositReceiptSampleOn) { + setLoading(true); + setTimeout(() => { + downloadImage(); + }, 300); + } + }, [depositReceiptSampleOn, depositInfo]); + + return ( + <> +
+
+
+
+
+ NICEPAY +
+
+
+
입금확인증
+
+
+
+
+
+ + + + + + + +
+
입금정보
+
+
+
입금일시
+
{depositInfo?.depositDate}
+
+
+
입금은행
+
{depositInfo?.depositBank}
+
+
+
입금계좌
+
{depositInfo?.depositAccount}
+
+
+
금액
+
+ +
+
+
+
입금적요
+
{depositInfo?.depositReason}
+
+
+
입금ID
+
{depositInfo?.depositId}
+
+
+
+
+
+ {!!loading && + <> +
+ + + } + + ); }; diff --git a/src/entities/transaction/ui/list-item.tsx b/src/entities/transaction/ui/list-item.tsx index dd925a7..dff0803 100644 --- a/src/entities/transaction/ui/list-item.tsx +++ b/src/entities/transaction/ui/list-item.tsx @@ -194,7 +194,7 @@ export const ListItem = ({ rs.push(
{ getTime() } | @@ -213,7 +213,10 @@ export const ListItem = ({ } else if(transactionCategory === TransactionCategory.CashReceipt){ rs.push( -
+
{ getTime() } | { transactionType } @@ -226,7 +229,10 @@ export const ListItem = ({ } else if(transactionCategory === TransactionCategory.Escrow){ rs.push( -
+
{ getTime() } | { deliveryStatus } @@ -246,7 +252,10 @@ export const ListItem = ({ } else if(transactionCategory === TransactionCategory.Billing){ rs.push( -
+
{ getTime() } | { processResult } diff --git a/src/entities/transaction/ui/section/amount-info-section.tsx b/src/entities/transaction/ui/section/amount-info-section.tsx index 89c01b3..44561c4 100644 --- a/src/entities/transaction/ui/section/amount-info-section.tsx +++ b/src/entities/transaction/ui/section/amount-info-section.tsx @@ -216,7 +216,9 @@ export const AmountInfoSection = ({ }; cashReceiptReceiptSendEamil(params).then((rs: CashReceiptReceiptSendEmailResponse) => { console.log(rs); - snackBar('이메일로 현금영수증 요청이 완료되었습니다.'); + if(rs.message){ + snackBar(rs.message); + } }).catch((e: any) => { if(e.response?.data?.error?.message){ snackBar(e.response?.data?.error?.message); diff --git a/src/entities/vat-return/api/use-vat-return-tax-invoice-send-email-mutation.ts b/src/entities/vat-return/api/use-vat-return-tax-invoice-send-email-mutation.ts index 08dc2d6..3fc3d93 100644 --- a/src/entities/vat-return/api/use-vat-return-tax-invoice-send-email-mutation.ts +++ b/src/entities/vat-return/api/use-vat-return-tax-invoice-send-email-mutation.ts @@ -3,8 +3,8 @@ import { API_URL_VAT_RETURN } from '@/shared/api/api-url-vat-return'; import { resultify } from '@/shared/lib/resultify'; import { NiceAxiosError } from '@/shared/@types/error'; import { - VatReturnTaxInvoiceSendEmailResponse, VatReturnTaxInvoiceSendEmailParams, + VatReturnTaxInvoiceSendEmailResponse } from '../model/types'; import { useMutation, diff --git a/src/entities/vat-return/ui/section/amount-section.tsx b/src/entities/vat-return/ui/section/amount-section.tsx index 657ecd1..34f88c8 100644 --- a/src/entities/vat-return/ui/section/amount-section.tsx +++ b/src/entities/vat-return/ui/section/amount-section.tsx @@ -55,7 +55,9 @@ export const AmountSection = ({ }; vatReturnTaxInvoiceSendEmail(params).then((rs: VatReturnTaxInvoiceSendEmailResponse) => { console.log(rs); - snackBar('이메일로 세금계산서 요청이 완료되었습니다.'); + if(rs.message){ + snackBar(rs.message); + } }).catch((e: any) => { if(e.response?.data?.error?.message){ snackBar(e.response?.data?.error?.message); diff --git a/src/pages/home/home-page.tsx b/src/pages/home/home-page.tsx index 22024d0..be3d6e2 100644 --- a/src/pages/home/home-page.tsx +++ b/src/pages/home/home-page.tsx @@ -117,11 +117,10 @@ export const HomePage = () => { else{ useStore.getState().UserStore.setFirstAccess(false); } - + let userFavorite = useStore.getState().UserStore.userFavorite; setFavoriteItems(userFavorite); callHomeBannerList(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const setBottomBannerEffect = (mode: boolean) => {