부가세 신고 페이지 및 컴포넌트 다국어화 완료

- 부가세 신고 페이지 다국어화 (3개 페이지)
  * 조회 페이지: 헤더 타이틀, 탭, 검색/다운로드
  * 상세 페이지: 세금계산서 상세 정보
  * 참조번호 발급 페이지: 신청 완료/실패 메시지
- 부가세 엔티티 컴포넌트 다국어화 (11개 컴포넌트)
  * vat-return-tab: 세금계산서/부가세참고 탭
  * list-wrap: 조회 페이지 래퍼
  * list-item: 거래 항목 (통화 표기 포함)
  * reference-wrap: 참조 자료 신청
  * reference-request-success/fail: 신청 결과
  * list-detail-bottom-sheet: 상세 내역 (10개 통화 인스턴스)
- 섹션 컴포넌트 다국어화 (4개)
  * amount-section: 금액 상세 (공급가액, VAT, 합계금액)
  * issue-section: 발행 정보 (발행대상일자, 적요 등)
  * supplier-section: 공급자 정보
  * receiver-section: 공급받는 자 정보
- 통화 표기 통일 (10개 인스턴스)
  * 한국어: 1,000원 (suffix)
  * 영어: ₩1,000 (prefix)
- 번역 키 추가: vatReturn 네임스페이스 29개 키
- 기존 번역 키 재사용 (merchant, transaction, common, home)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jay Sheen
2025-10-30 09:25:50 +09:00
parent 04fc07cfe5
commit bc1540ca04
16 changed files with 234 additions and 129 deletions

View File

@@ -5,6 +5,7 @@ import { NumericFormat } from "react-number-format";
import { SlideDown } from 'react-slidedown';
import 'react-slidedown/lib/slidedown.css';
import { useEffect, useState } from "react";
import { useTranslation } from 'react-i18next';
export interface VatReturnListDetailBottomSheetProps {
bottomSheetOn: boolean;
@@ -25,7 +26,7 @@ export const VatReturnListDetailBottomSheet = ({
vatAmount,
totalAmount
}: VatReturnListDetailBottomSheetProps) => {
const { t, i18n } = useTranslation();
const [isOpenDetail, setIsOpenDetail] = useState<Array<boolean>>([]);
const onClickToClose = () => {
@@ -48,14 +49,14 @@ export const VatReturnListDetailBottomSheet = ({
<div className="bottomsheet">
<div className="bottomsheet-header">
<div className="bottomsheet-title">
<h2> </h2>
<button
className="close-btn"
<h2>{t('vatReturn.viewDetails')}</h2>
<button
className="close-btn"
type="button"
>
<img
<img
src={ IMAGE_ROOT +'/ico_close.svg' }
alt="닫기"
alt={t('common.close')}
onClick={ onClickToClose }
/>
</button>
@@ -66,46 +67,50 @@ export const VatReturnListDetailBottomSheet = ({
<div className="tax-detail-accordion">
<div className="summary">
<div className="row">
<div className="label desc dot"> :</div>
<div className="label desc dot">{t('vatReturn.transactionAmount')} :</div>
<div className="value">
<NumericFormat
value={ transactionAmount }
value={ transactionAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</div>
</div>
<div className="row">
<div className="label desc dot"> :</div>
<div className="label desc dot">{t('vatReturn.supplyAmount')} :</div>
<div className="value">
<NumericFormat
value={ supplyAmount }
value={ supplyAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</div>
</div>
<div className="row">
<div className="label desc dot">VAT :</div>
<div className="label desc dot">{t('vatReturn.vat')} :</div>
<div className="value">
<NumericFormat
value={ vatAmount }
value={ vatAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</div>
</div>
<div className="row">
<div className="label desc dot"> :</div>
<div className="label desc dot">{t('vatReturn.totalAmount')} :</div>
<div className="value">
<NumericFormat
value={ totalAmount }
value={ totalAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</div>
</div>
@@ -113,8 +118,8 @@ export const VatReturnListDetailBottomSheet = ({
{ breakdown && breakdown.length > 0 &&
<div className="list">
<div className="list-header">
<div className="head-date"></div>
<div className="head-amount"></div>
<div className="head-date">{t('vatReturn.transactionDate')}</div>
<div className="head-amount">{t('vatReturn.totalAmount')}</div>
</div>
{
breakdown.map((value, index) => (
@@ -125,14 +130,15 @@ export const VatReturnListDetailBottomSheet = ({
<div className="amount">
<span className="text">
<NumericFormat
value={ value.totalAmount }
value={ value.totalAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</span>
<img
className="arrow down"
<img
className="arrow down"
src={ IMAGE_ROOT + '/ico_arrow.svg' }
onClick={ () => openDetail(index) }
/>
@@ -142,33 +148,36 @@ export const VatReturnListDetailBottomSheet = ({
{ isOpenDetail[index] &&
<div className="item item-detail">
<div className="labels">
<span></span>
<span></span>
<span>VAT</span>
<span>{t('vatReturn.transactionAmount')}</span>
<span>{t('vatReturn.supplyAmount')}</span>
<span>{t('vatReturn.vat')}</span>
</div>
<div className="values">
<span>
<NumericFormat
value={ value.transactionAmount }
value={ value.transactionAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</span>
<span>
<NumericFormat
value={ value.supplyAmount }
value={ value.supplyAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</span>
<span>
<NumericFormat
value={ value.vatAmount }
value={ value.vatAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</span>
</div>

View File

@@ -1,4 +1,5 @@
import { NumericFormat } from 'react-number-format';
import { useTranslation } from 'react-i18next';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { ListItemProps } from '../model/types';
@@ -12,6 +13,7 @@ export const ListItem = ({
paymentMethod,
amount
}: ListItemProps) => {
const { t, i18n } = useTranslation();
const { navigate } = useNavigate();
const onClickToNavigate = () => {
@@ -41,11 +43,12 @@ export const ListItem = ({
</div>
<div className="transaction-amount">
<NumericFormat
value={ amount }
value={ amount }
thousandSeparator
displayType="text"
suffix='원'
></NumericFormat>
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</div>
</div>
</>

View File

@@ -1,16 +1,17 @@
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { ListFilter } from './filter/list-filter';
import { SortTypeBox } from '@/entities/common/ui/sort-type-box';
import { DefaultRequestPagination, SortTypeKeys } from '@/entities/common/model/types';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant';
import {
VatReturnListContent,
VatReturnListParams,
VatReturnListResponse,
VatReturnReceiptType,
VatReturnTargetType
import {
VatReturnListContent,
VatReturnListParams,
VatReturnListResponse,
VatReturnReceiptType,
VatReturnTargetType
} from '../model/types';
import { useVatReturnListMutation } from '../api/use-vat-return-list-mutation';
import { ListDateGroup } from './list-date-group';
@@ -19,6 +20,7 @@ import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import useIntersectionObserver from '@/widgets/intersection-observer';
export const ListWrap = () => {
const { t } = useTranslation();
const userMid = useStore.getState().UserStore.mid;
const [onActionIntersect, setOnActionIntersect] = useState<boolean>(false);
@@ -175,20 +177,20 @@ export const ListWrap = () => {
value={ moment(startMonth+'01').format('YYYY.MM') + '-' + moment(endMonth+'01').format('YYYY.MM')}
readOnly={ true }
/>
<button
<button
className="filter-btn"
onClick={ () => onClickToOpenFIlter() }
>
<img
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
alt="검색옵션"
alt={t('transaction.searchOptions')}
/>
</button>
</div>
<button className="download-btn">
<img
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
alt="다운로드"
alt={t('transaction.download')}
onClick={ onClickToDownloadExcel }
/>
</button>

View File

@@ -1,5 +1,6 @@
import { motion } from 'framer-motion';
import {
import { useTranslation } from 'react-i18next';
import {
FilterMotionDuration,
FilterMotionStyle,
FilterMotionVariants
@@ -16,6 +17,7 @@ export const ReferenceRequestFail = ({
setPageOn,
errorMsg
}: ReferenceRequestFailProps) => {
const { t } = useTranslation();
const onClickToClose = () => {
setPageOn(false);
};
@@ -35,27 +37,27 @@ export const ReferenceRequestFail = ({
style={{ margin: '0 auto' }}
>
<div className="success-body">
<div
className="error-icon"
<div
className="error-icon"
aria-hidden="true"
></div>
<h1 className="success-title">
<span> </span>
<span>{t('vatReturn.vatReferenceData')}</span>
<br/>
<span> </span>
<span>{t('vatReturn.requestFailed')}</span>
</h1>
<div className="success-result">
<p className="result-text align-left position_label">
<span> :</span>
<span>{t('vatReturn.result')} :</span>
<span>{ errorMsg }</span>
</p>
</div>
</div>
<div className="apply-row">
<button
<button
className="btn-50 btn-blue flex-1"
onClick={ onClickToClose }
></button>
>{t('common.confirm')}</button>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import { motion } from 'framer-motion';
import {
import { useTranslation } from 'react-i18next';
import {
FilterMotionDuration,
FilterMotionStyle,
FilterMotionVariants
@@ -19,6 +20,7 @@ export const ReferenceRequestSuccess = ({
email,
startDate
}: ReferenceRequestSuccessProps) => {
const { t } = useTranslation();
const onClickToClose = () => {
setPageOn(false);
};
@@ -33,31 +35,31 @@ export const ReferenceRequestSuccess = ({
style={ FilterMotionStyle }
>
<div className="full-menu-container">
<div
<div
className="success-body"
style={{ margin: '0 auto' }}
>
<div className="success-icon" aria-hidden="true"></div>
<h1 className="success-title">
<span> </span>
<span>{t('vatReturn.vatReferenceData')}</span>
<br/>
<span> .</span>
<span>{t('vatReturn.requestCompleted')}</span>
</h1>
<p className="success-subtitle"> .</p>
<p className="success-subtitle">{t('vatReturn.fileProvidedBySelectedMethod')}</p>
<div className="success-result">
<p className="result-text">
<span> :</span> <span>{ moment(startDate).format('YYYY.MM.DD') }</span>
<span>{t('vatReturn.applicationDate')} :</span> <span>{ moment(startDate).format('YYYY.MM.DD') }</span>
<br/>
<span> :</span> <span>{ email }</span>
<span>{t('vatReturn.emailAddress')} :</span> <span>{ email }</span>
</p>
</div>
<p className="success-note dot"> <br/> .</p>
<p className="success-note dot">{t('vatReturn.referenceDataNote')}</p>
</div>
<div className="apply-row">
<button
<button
className="btn-50 btn-blue flex-1"
onClick={ onClickToClose }
></button>
>{t('common.confirm')}</button>
</div>
</div>
</motion.div>

View File

@@ -1,5 +1,6 @@
import moment from 'moment';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FilterCalendar } from '@/shared/ui/filter/calendar';
import { FilterSelect } from '@/shared/ui/filter/select';
import { ReferenceRequestSuccess } from './reference-request-success';
@@ -11,6 +12,7 @@ import { useStore } from '@/shared/model/store';
import { useVatReturnReferenceRequestMutation } from '../api/use-vat-return-reference-request-mutation';
export const ReferenceWrap = () => {
const { t } = useTranslation();
const midOptions = useStore.getState().UserStore.selectOptionsMids;
const emailOptions = useStore.getState().UserStore.selectOptionsEmails;
const userMid = useStore.getState().UserStore.mid;
@@ -48,13 +50,13 @@ export const ReferenceWrap = () => {
<>
<div className="option-list no-padding pt-30">
<FilterSelect
title='가맹점'
title={t('merchant.title')}
selectValue={ mid }
selectSetter={ setMid }
selectOptions={ midOptions }
></FilterSelect>
<FilterButtonGroups
title='거래 과세/면세 구분'
title={t('vatReturn.taxExemptionType')}
activeValue={ payTax }
btnGroups={ VatReturnTaxBtnGroups }
setter={ setPayTax }
@@ -62,24 +64,24 @@ export const ReferenceWrap = () => {
maxBtn={ 2 }
></FilterButtonGroups>
<FilterCalendar
title='거래기간'
title={t('vatReturn.transactionPeriod')}
startDate={ startDate }
endDate={ endDate }
setStartDate={ setStartDate }
setEndDate={ setEndDate }
></FilterCalendar>
<FilterSelect
title='수령메일주소'
title={t('vatReturn.receivingEmail')}
selectValue={ email }
selectSetter={ setEmail }
selectOptions={ emailOptions }
></FilterSelect>
</div>
<div className="apply-row">
<button
<button
className="btn-50 btn-blue flex-1"
onClick={ onClickToResquest }
></button>
>{t('vatReturn.requestData')}</button>
</div>
<ReferenceRequestSuccess
pageOn={ successPageOn }

View File

@@ -1,4 +1,5 @@
import moment from 'moment';
import { useTranslation } from 'react-i18next';
import { VatReturnDetailResponse } from '../../model/types';
import { SectionTitleArrow } from '@/entities/common/ui/section-title-arrow';
import { NumericFormat } from 'react-number-format';
@@ -14,6 +15,7 @@ export interface AmountSectionProps {
export const AmountSection = ({
detail
}: AmountSectionProps) => {
const { t, i18n } = useTranslation();
const [isOpen, setIsOpen] = useState<boolean>(false);
const [downloadBottomSheetOn, setDownloadBottomSheetOn] = useState<boolean>(false);
@@ -35,44 +37,47 @@ export const AmountSection = ({
<div className="txn-num-group">
<div className="txn-amount">
<div className="value">
{i18n.language === 'en' && <span>{t('home.currencySymbol')}</span>}
<NumericFormat
value={ detail.totalAmount }
value={ detail.totalAmount }
thousandSeparator
displayType="text"
></NumericFormat>
<span className="unit"></span>
<span className="unit">{i18n.language === 'en' ? '' : t('home.currencyWon')}</span>
</div>
<button
className="chip-btn"
<button
className="chip-btn"
type="button"
onClick={ () => openSection() }
>
<span></span> <SectionTitleArrow isOpen={ isOpen }></SectionTitleArrow>
<span>{t('vatReturn.amountDetail')}</span> <SectionTitleArrow isOpen={ isOpen }></SectionTitleArrow>
</button>
</div>
<SlideDown className={'my-dropdown-slidedown'}>
{ isOpen &&
{ isOpen &&
<div className="amount-expand">
<ul className="amount-list">
<li className="amount-item">
<span className="label">·&nbsp;&nbsp;</span>
<span className="label">·&nbsp;&nbsp;{t('vatReturn.supplyAmount')}</span>
<span className="value">
<NumericFormat
value={ detail.supplyAmount }
value={ detail.supplyAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</span>
</li>
<li className="amount-item">
<span className="label">·&nbsp;&nbsp;VAT</span>
<span className="label">·&nbsp;&nbsp;{t('vatReturn.vat')}</span>
<span className="value">
<NumericFormat
value={ detail.vatAmount }
value={ detail.vatAmount }
thousandSeparator
displayType="text"
suffix='원'
prefix={i18n.language === 'en' ? t('home.currencySymbol') : ''}
suffix={i18n.language === 'en' ? '' : t('home.currencyWon')}
></NumericFormat>
</span>
</li>
@@ -85,17 +90,17 @@ export const AmountSection = ({
</div>
<div className="txn-mid">
<span className="value">
{ !!detail.issueDate &&
moment(detail.issueDate).format('YYYY.MM.DD')
{ !!detail.issueDate &&
moment(detail.issueDate).format('YYYY.MM.DD')
}
</span>
</div>
<div className="txn-doc">
<button
className="doc-btn"
<button
className="doc-btn"
type="button"
onClick={ onClickToOpenDownloadBottomSheet }
></button>
>{t('vatReturn.taxInvoice')}</button>
</div>
</div>
{ !!downloadBottomSheetOn &&

View File

@@ -1,4 +1,5 @@
import moment from 'moment';
import { useTranslation } from 'react-i18next';
import { VatReturnDetailResponse } from '../../model/types';
export interface IssueSectionProps {
@@ -8,46 +9,47 @@ export interface IssueSectionProps {
export const IssueSection = ({
detail
}: IssueSectionProps) => {
const { t } = useTranslation();
return (
<>
<div className="txn-section">
<div className="section-title"> </div>
<div className="section-title">{t('vatReturn.issueInfo')}</div>
<ul className="kv-list">
<li className="kv-row">
<span className="k">MID</span>
<span className="v">{ detail.mid }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('vatReturn.issueTargetDate')}</span>
<span className="v">
{ !!detail.targetBusinessStartDate && !!detail.targetBusinessEndDate &&
moment(detail.targetBusinessStartDate).format('YYYY.MM.DD')
+
' ~ '
+
moment(detail.targetBusinessEndDate).format('YYYY.MM.DD')
moment(detail.targetBusinessStartDate).format('YYYY.MM.DD')
+
' ~ '
+
moment(detail.targetBusinessEndDate).format('YYYY.MM.DD')
}
</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('vatReturn.issueDate')}</span>
<span className="v">
{ !!detail.issueDate &&
moment(detail.issueDate).format('YYYY.MM.DD')
moment(detail.issueDate).format('YYYY.MM.DD')
}
</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('vatReturn.subject')}</span>
<span className="v">{ detail.subject }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('vatReturn.issueTarget')}</span>
<span className="v">{ detail.targetType }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('vatReturn.receiptType')}</span>
<span className="v">{ detail.receiptType }</span>
</li>
<li className="kv-row">

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { VatReturnDetailResponse } from '../../model/types';
export interface ReceiverSectionProps {
@@ -7,23 +8,24 @@ export interface ReceiverSectionProps {
export const ReceiverSection = ({
detail
}: ReceiverSectionProps) => {
const { t } = useTranslation();
return (
<>
<div className="txn-section">
<div className="section-title"> </div>
<div className="section-title">{t('vatReturn.receiverInfo')}</div>
</div>
<ul className="kv-list">
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('merchant.businessRegistrationNumber')}</span>
<span className="v">{ detail.receiverBusinessRegistrationNumber }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('merchant.companyName')}</span>
<span className="v">{ detail.receiverCompanyName }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('merchant.representativeName')}</span>
<span className="v">{ detail.receiverCeoName }</span>
</li>
</ul>

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { VatReturnDetailResponse } from '../../model/types';
export interface SupplierSectionProps {
@@ -7,23 +8,24 @@ export interface SupplierSectionProps {
export const SupplierSection = ({
detail
}: SupplierSectionProps) => {
const { t } = useTranslation();
return (
<>
<div className="txn-section">
<div className="section-title"> </div>
<div className="section-title">{t('vatReturn.supplierInfo')}</div>
</div>
<ul className="kv-list">
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('merchant.businessRegistrationNumber')}</span>
<span className="v">{ detail.supplierBusinessRegistrationNumber }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('merchant.companyName')}</span>
<span className="v">{ detail.supplierCompanyName }</span>
</li>
<li className="kv-row">
<span className="k"></span>
<span className="k">{t('merchant.representativeName')}</span>
<span className="v">{ detail.supplierCeoName }</span>
</li>
</ul>

View File

@@ -1,13 +1,15 @@
import { useTranslation } from 'react-i18next';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import {
VatReturnTabKeys,
VatReturnTabProps
import {
VatReturnTabKeys,
VatReturnTabProps
} from '../model/types';
export const VatReturnTab = ({
activeTab
}: VatReturnTabProps) => {
const { t } = useTranslation();
const { navigate } = useNavigate();
const onClickToNavigation = (tab: VatReturnTabKeys) => {
@@ -23,14 +25,14 @@ export const VatReturnTab = ({
return(
<>
<div className="subTab">
<button
<button
className={`subtab-btn ${(activeTab === VatReturnTabKeys.List)? 'active': ''}` }
onClick={ () => onClickToNavigation(VatReturnTabKeys.List) }
> </button>
<button
onClick={ () => onClickToNavigation(VatReturnTabKeys.List) }
>{t('vatReturn.taxInvoice')}</button>
<button
className={`subtab-btn ${(activeTab === VatReturnTabKeys.VatReference)? 'active': ''}` }
onClick={ () => onClickToNavigation(VatReturnTabKeys.VatReference) }
> </button>
onClick={ () => onClickToNavigation(VatReturnTabKeys.VatReference) }
>{t('vatReturn.vatReference')}</button>
</div>
</>
);