From d9856ea41d75b5d204e09e15989a2d763c79ca38 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Oct 2024 13:57:38 +0800 Subject: [PATCH 1/5] Feat: enhance counterparty --- package.json | 2 +- public/icons/asset.svg | 7 ++ src/components/voucher/new_voucher_form.tsx | 133 +++++++++++++------- src/locales/cn/common.json | 1 + src/locales/cn/journal.json | 3 +- src/locales/en/common.json | 1 + src/locales/en/journal.json | 3 +- src/locales/tw/common.json | 1 + src/locales/tw/journal.json | 3 +- 9 files changed, 106 insertions(+), 48 deletions(-) create mode 100644 public/icons/asset.svg diff --git a/package.json b/package.json index 47ed8048f..038ad00ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+37", + "version": "0.8.2+38", "private": false, "scripts": { "dev": "next dev", diff --git a/public/icons/asset.svg b/public/icons/asset.svg new file mode 100644 index 000000000..07d9a9375 --- /dev/null +++ b/public/icons/asset.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/voucher/new_voucher_form.tsx b/src/components/voucher/new_voucher_form.tsx index e2de672af..d01151e2e 100644 --- a/src/components/voucher/new_voucher_form.tsx +++ b/src/components/voucher/new_voucher_form.tsx @@ -1,23 +1,24 @@ import React, { useState, useEffect, useRef } from 'react'; +import Image from 'next/image'; import { useRouter } from 'next/router'; import { FaChevronDown, FaPlus } from 'react-icons/fa6'; import { BiSave } from 'react-icons/bi'; -import { FiSearch } from 'react-icons/fi'; +import { FiPlus, FiSearch } from 'react-icons/fi'; import { useTranslation } from 'next-i18next'; import useOuterClick from '@/lib/hooks/use_outer_click'; +import { numberWithCommas } from '@/lib/utils/common'; import VoucherLineItem from '@/components/voucher/voucher_line_item'; import { Button } from '@/components/button/button'; import DatePicker, { DatePickerType } from '@/components/date_picker/date_picker'; import Toggle from '@/components/toggle/toggle'; import { IDatePeriod } from '@/interfaces/date_period'; +import { IAccount } from '@/interfaces/accounting_account'; +import { MessageType } from '@/interfaces/message_modal'; import { checkboxStyle, default30DayPeriodInSec } from '@/constants/display'; import { VoucherType } from '@/constants/account'; import { useUserCtx } from '@/contexts/user_context'; import { useAccountingCtx } from '@/contexts/accounting_context'; -import { IAccount } from '@/interfaces/accounting_account'; -import { numberWithCommas } from '@/lib/utils/common'; import { useModalContext } from '@/contexts/modal_context'; -import { MessageType } from '@/interfaces/message_modal'; interface ILineItem { id: number; @@ -37,6 +38,8 @@ enum RecurringUnit { WEEK = 'week', } +const AccountTitlesOfAPandAR = ['應收帳款', '應付帳款']; + const NewVoucherForm: React.FC = () => { const { t } = useTranslation('common'); const router = useRouter(); @@ -90,45 +93,50 @@ const NewVoucherForm: React.FC = () => { }, ]; - // Info: (20241004 - Julian) form state + // Info: (20241004 - Julian) 通用項目 const [date, setDate] = useState(default30DayPeriodInSec); const [type, setType] = useState(VoucherType.EXPENSE); const [note, setNote] = useState(''); - // Info: (20241004 - Julian) counterparty state - const [counterKeyword, setCounterKeyword] = useState(''); - const [counterparty, setCounterparty] = useState( - t('journal:ADD_NEW_VOUCHER.COUNTERPARTY') - ); - const [filteredCounterparty, setFilteredCounterparty] = - useState(dummyCounterparty); - - // Info: (20241004 - Julian) recurring state + // Info: (20241004 - Julian) 週期性分錄相關 state const [isRecurring, setIsRecurring] = useState(false); const [recurringPeriod, setRecurringPeriod] = useState(default30DayPeriodInSec); const [recurringUnit, setRecurringUnit] = useState(RecurringUnit.MONTH); const [recurringArray, setRecurringArray] = useState([]); - // Info: (20241004 - Julian) voucher line state + // Info: (20241004 - Julian) 傳票列 const [lineItems, setLineItems] = useState([initialVoucherLine]); - // Info: (20241004 - Julian) verification state + // Info: (20241004 - Julian) 傳票列驗證條件 const [totalCredit, setTotalCredit] = useState(0); const [totalDebit, setTotalDebit] = useState(0); const [haveZeroLine, setHaveZeroLine] = useState(false); const [isAccountingNull, setIsAccountingNull] = useState(false); - // Info: (20241004 - Julian) style state - const [isShowDateHint, setIsShowDateHint] = useState(false); - const [isShowCounterHint, setIsShowCounterHint] = useState(false); - const [isShowRecurringPeriodHint, setIsShowRecurringPeriodHint] = useState(false); - const [isShowRecurringArrayHint, setIsShowRecurringArrayHint] = useState(false); - // Info: (20241004 - Julian) 清空表單 flag const [flagOfClear, setFlagOfClear] = useState(false); // Info: (20241007 - Julian) 送出表單 flag const [flagOfSubmit, setFlagOfSubmit] = useState(false); + // Info: (20241009 - Julian) 追加項目 + const [isCounterpartyRequired, setIsCounterpartyRequired] = useState(false); + // const [isAssetRequired, setIsAssetRequired] = useState(false); + // const [isReverseRequired, setIsReverseRequired] = useState(false); + + // Info: (20241004 - Julian) 交易對象相關 state + const [counterKeyword, setCounterKeyword] = useState(''); + const [counterparty, setCounterparty] = useState( + t('journal:ADD_NEW_VOUCHER.COUNTERPARTY') + ); + const [filteredCounterparty, setFilteredCounterparty] = + useState(dummyCounterparty); + + // Info: (20241004 - Julian) 是否顯示提示 + const [isShowDateHint, setIsShowDateHint] = useState(false); + const [isShowCounterHint, setIsShowCounterHint] = useState(false); + const [isShowRecurringPeriodHint, setIsShowRecurringPeriodHint] = useState(false); + const [isShowRecurringArrayHint, setIsShowRecurringArrayHint] = useState(false); + // Info: (20241004 - Julian) Type 下拉選單 const { targetRef: typeRef, @@ -176,10 +184,16 @@ const NewVoucherForm: React.FC = () => { // Info: (20241004 - Julian) 檢查是否有未選擇的會計科目 const accountingNull = lineItems.some((item) => item.account === null); + // Info: (20241009 - Julian) 會計科目有應收付帳款時,顯示 Counterparty + const isAPorAR = lineItems.some((item) => + AccountTitlesOfAPandAR.includes(item.account?.name || '')); + setTotalDebit(debitTotal); setTotalCredit(creditTotal); setHaveZeroLine(zeroLine); setIsAccountingNull(accountingNull); + + setIsCounterpartyRequired(isAPorAR); }, [lineItems]); useEffect(() => { @@ -237,6 +251,7 @@ const NewVoucherForm: React.FC = () => { } }, [isRecurring, recurringPeriod]); + // Info: (20241007 - Julian) 週期未選擇時顯示提示 useEffect(() => { if (isRecurring && recurringArray.length > 0) { setIsShowRecurringArrayHint(false); @@ -308,7 +323,7 @@ const NewVoucherForm: React.FC = () => { setFlagOfClear(!flagOfClear); }; - // Info: (20241004 - Julian) Message Modal + // Info: (20241004 - Julian) 清空表單前的警告提示 const clearClickHandler = () => { messageModalDataHandler({ messageType: MessageType.WARNING, @@ -356,15 +371,18 @@ const NewVoucherForm: React.FC = () => { // Info: (20241007 - Julian) 日期不可為 0:顯示日期提示,並定位到日期欄位 setIsShowDateHint(true); router.push('#voucher-date'); - } else if (counterparty === '' || counterparty === t('journal:ADD_NEW_VOUCHER.COUNTERPARTY')) { - // Info: (20241004 - Julian) 交易對象不可為空:顯示類型提示,並定位到類型欄位 + } else if ( + // Info: (20241004 - Julian) 如果需填入交易對象,則交易對象不可為空:顯示類型提示,並定位到類型欄位 + isCounterpartyRequired && + (counterparty === '' || counterparty === t('journal:ADD_NEW_VOUCHER.COUNTERPARTY')) + ) { setIsShowCounterHint(true); router.push('#voucher-counterparty'); } else if ( + // Info: (20241007 - Julian) 如果開啟週期,但週期區間未選擇,則顯示週期提示,並定位到週期欄位 isRecurring && (recurringPeriod.startTimeStamp === 0 || recurringPeriod.endTimeStamp === 0) ) { - // Info: (20241007 - Julian) 如果開啟週期,但週期區間未選擇,則顯示週期提示,並定位到週期欄位 setIsShowRecurringPeriodHint(true); router.push('#voucher-recurring'); } else if (isRecurring && recurringArray.length === 0) { @@ -580,7 +598,7 @@ const NewVoucherForm: React.FC = () => { className="py-8px hover:bg-dropdown-surface-menu-background-secondary" onClick={recurringUnitClickHandler} > - {unit} + {t(`common:COMMON.${unit.toUpperCase()}`)} ); })} @@ -695,24 +713,26 @@ const NewVoucherForm: React.FC = () => { /> {/* Info: (20240926 - Julian) Counterparty */} -
-

- {t('journal:ADD_NEW_VOUCHER.COUNTERPARTY')} - * -

-
- {displayedCounterparty} -
- + {isCounterpartyRequired && ( +
+

+ {t('journal:ADD_NEW_VOUCHER.COUNTERPARTY')} + * +

+
+ {displayedCounterparty} +
+ +
+ {/* Info: (20241004 - Julian) Counterparty drop menu */} + {counterpartyDropMenu}
- {/* Info: (20241004 - Julian) Counterparty drop menu */} - {counterpartyDropMenu} -
+ )} {/* Info: (20241007 - Julian) Recurring */}
{/* Info: (20241007 - Julian) switch */} @@ -740,7 +760,9 @@ const NewVoucherForm: React.FC = () => { > {/* Info: (20241007 - Julian) recurring unit block */}
-

Every

+

+ {t('journal:ADD_NEW_VOUCHER.EVERY')} +

{
+ {/* Info: (20241009 - Julian) Asset */} +
+ {/* Info: (20241009 - Julian) Asset Divider */} +
+
+
+ asset_icon +

Asset

+
+
+
+ {/* Info: (20241009 - Julian) Asset block */} +
+
+

Empty

+

Please add at least 1 asset!

+
+ +
+
{/* Info: (20240926 - Julian) voucher line block */} {voucherLineBlock} {/* Info: (20240926 - Julian) buttons */} diff --git a/src/locales/cn/common.json b/src/locales/cn/common.json index 787c7a880..8ed619364 100644 --- a/src/locales/cn/common.json +++ b/src/locales/cn/common.json @@ -267,6 +267,7 @@ "BUSINESS_REGISTRATION_NUMBER": "商业登记号码", "EMPTY": "无资料", "STATUS": "状态", + "WEEK": "周", "MONTH": "月", "YEAR": "年", "SELECT": "选择", diff --git a/src/locales/cn/journal.json b/src/locales/cn/journal.json index b634a0606..00fdb8489 100644 --- a/src/locales/cn/journal.json +++ b/src/locales/cn/journal.json @@ -234,7 +234,8 @@ "RECURRING_ENTRY": "周期性分录", "SELECT_ACCOUNTING": "选择会计科目", "NO_ACCOUNTING_FOUND": "查无会计科目", - "NO_COUNTERPARTY_FOUND": "查无交易对象" + "NO_COUNTERPARTY_FOUND": "查无交易对象", + "EVERY": "每" }, "ACCOUNT_TYPE": { "ASSET": "资产", diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 49d080eb8..cf4ef7c71 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -267,6 +267,7 @@ "BUSINESS_REGISTRATION_NUMBER": "Business Registration Number", "EMPTY": "Empty", "STATUS": "Status", + "WEEK": "Week", "MONTH": "Month", "YEAR": "Year", "SELECT": "Select", diff --git a/src/locales/en/journal.json b/src/locales/en/journal.json index 27fc5675a..e1eaff797 100644 --- a/src/locales/en/journal.json +++ b/src/locales/en/journal.json @@ -234,7 +234,8 @@ "RECURRING_ENTRY": "Recurring Entry", "SELECT_ACCOUNTING": "Select Accounting", "NO_ACCOUNTING_FOUND": "No accounting found", - "NO_COUNTERPARTY_FOUND": "No counterparty found" + "NO_COUNTERPARTY_FOUND": "No counterparty found", + "EVERY": "Every" }, "ACCOUNT_TYPE": { "ASSET": "Assets", diff --git a/src/locales/tw/common.json b/src/locales/tw/common.json index 8228037e8..b46bbc704 100644 --- a/src/locales/tw/common.json +++ b/src/locales/tw/common.json @@ -267,6 +267,7 @@ "BUSINESS_REGISTRATION_NUMBER": "商業登記號碼", "EMPTY": "無資料", "STATUS": "狀態", + "WEEK": "週", "MONTH": "月", "YEAR": "年", "SELECT": "選擇", diff --git a/src/locales/tw/journal.json b/src/locales/tw/journal.json index 84b94f610..4fe1abfb6 100644 --- a/src/locales/tw/journal.json +++ b/src/locales/tw/journal.json @@ -234,7 +234,8 @@ "RECURRING_ENTRY": "週期性分錄", "SELECT_ACCOUNTING": "選擇會計科目", "NO_ACCOUNTING_FOUND": "查無會計科目", - "NO_COUNTERPARTY_FOUND": "查無交易對象" + "NO_COUNTERPARTY_FOUND": "查無交易對象", + "EVERY": "每" }, "ACCOUNT_TYPE": { "ASSET": "資產", From 50899cd878292de5884050176295b7efea9388e9 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Oct 2024 18:13:49 +0800 Subject: [PATCH 2/5] Feat: develop asset section & reverse section --- package.json | 2 +- public/icons/number_sign.svg | 3 + src/components/voucher/asset_section.tsx | 94 ++++++++++ src/components/voucher/new_voucher_form.tsx | 108 ++++------- src/components/voucher/reverse_section.tsx | 188 +++++++++++++++++++ src/components/voucher/voucher_line_item.tsx | 8 +- src/constants/asset.ts | 64 +++++++ src/constants/display.ts | 8 + src/interfaces/counterparty.ts | 34 ++++ 9 files changed, 432 insertions(+), 77 deletions(-) create mode 100644 public/icons/number_sign.svg create mode 100644 src/components/voucher/asset_section.tsx create mode 100644 src/components/voucher/reverse_section.tsx create mode 100644 src/interfaces/counterparty.ts diff --git a/package.json b/package.json index 038ad00ab..9e140cedf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+38", + "version": "0.8.2+39", "private": false, "scripts": { "dev": "next dev", diff --git a/public/icons/number_sign.svg b/public/icons/number_sign.svg new file mode 100644 index 000000000..a6ad5472e --- /dev/null +++ b/public/icons/number_sign.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/voucher/asset_section.tsx b/src/components/voucher/asset_section.tsx new file mode 100644 index 000000000..0d58ea807 --- /dev/null +++ b/src/components/voucher/asset_section.tsx @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; +import Image from 'next/image'; +import { FiEdit, FiPlus, FiTrash2 } from 'react-icons/fi'; +import { Button } from '@/components/button/button'; +import { IAssetItem, mockAssetItem } from '@/interfaces/asset'; + +const AssetSection: React.FC = () => { + const [assets, setAssets] = useState([]); + + // ToDo: (20241009 - Julian) Replace with real function to add asset + const generateRandomAsset = () => { + const newId = assets[assets.length - 1]?.id || 0; + const randomNum = Math.random() * 10; + const randomAssetName = + randomNum > 8 + ? 'MacBook Pro' + : randomNum > 5 + ? 'Printer' + : randomNum > 2 + ? 'Monitor' + : 'Keyboard'; + + const randomAsset: IAssetItem = { + ...mockAssetItem, + id: newId, + assetName: randomAssetName, + assetNumber: `A - ${newId.toFixed(0).padStart(6, '0')}`, + }; + + setAssets([...assets, randomAsset]); + }; + + const displayedAssetList = + assets && assets.length > 0 ? ( + assets.map((asset) => { + const deleteHandler = () => setAssets(assets.filter((a) => a.id !== asset.id)); + + return ( +
+ number_sign +
+

{asset.assetName}

+

{asset.assetNumber}

+
+
+ + +
+
+ ); + }) + ) : ( +
+

Empty

+

Please add at least 1 asset!

+
+ ); + + return ( + <> + {/* Info: (20241009 - Julian) Asset Divider */} +
+
+
+ asset_icon +

Asset

+
+
+
+ +
+ {displayedAssetList} + +
+ + ); +}; + +export default AssetSection; diff --git a/src/components/voucher/new_voucher_form.tsx b/src/components/voucher/new_voucher_form.tsx index d01151e2e..668afd198 100644 --- a/src/components/voucher/new_voucher_form.tsx +++ b/src/components/voucher/new_voucher_form.tsx @@ -1,9 +1,8 @@ import React, { useState, useEffect, useRef } from 'react'; -import Image from 'next/image'; import { useRouter } from 'next/router'; import { FaChevronDown, FaPlus } from 'react-icons/fa6'; import { BiSave } from 'react-icons/bi'; -import { FiPlus, FiSearch } from 'react-icons/fi'; +import { FiSearch } from 'react-icons/fi'; import { useTranslation } from 'next-i18next'; import useOuterClick from '@/lib/hooks/use_outer_click'; import { numberWithCommas } from '@/lib/utils/common'; @@ -11,14 +10,18 @@ import VoucherLineItem from '@/components/voucher/voucher_line_item'; import { Button } from '@/components/button/button'; import DatePicker, { DatePickerType } from '@/components/date_picker/date_picker'; import Toggle from '@/components/toggle/toggle'; +import AssetSection from '@/components/voucher/asset_section'; +import ReverseSection from '@/components/voucher/reverse_section'; import { IDatePeriod } from '@/interfaces/date_period'; import { IAccount } from '@/interfaces/accounting_account'; import { MessageType } from '@/interfaces/message_modal'; -import { checkboxStyle, default30DayPeriodInSec } from '@/constants/display'; -import { VoucherType } from '@/constants/account'; +import { ICounterparty, dummyCounterparty } from '@/interfaces/counterparty'; import { useUserCtx } from '@/contexts/user_context'; import { useAccountingCtx } from '@/contexts/accounting_context'; import { useModalContext } from '@/contexts/modal_context'; +import { checkboxStyle, inputStyle, default30DayPeriodInSec } from '@/constants/display'; +import { VoucherType } from '@/constants/account'; +import { AccountCodesOfAPandAR, AccountCodesOfAsset } from '@/constants/asset'; interface ILineItem { id: number; @@ -27,30 +30,16 @@ interface ILineItem { debit: number; credit: number; } -interface ICounterparty { - id: number; - code: string; - name: string; -} enum RecurringUnit { MONTH = 'month', WEEK = 'week', } -const AccountTitlesOfAPandAR = ['應收帳款', '應付帳款']; - const NewVoucherForm: React.FC = () => { const { t } = useTranslation('common'); const router = useRouter(); - const inputStyle = { - NORMAL: - 'border-input-stroke-input text-input-text-input-filled placeholder:text-input-text-input-placeholder disabled:text-input-text-input-placeholder', - ERROR: - 'border-input-text-error text-input-text-error placeholder:text-input-text-error disabled:text-input-text-error', - }; - const { selectedCompany } = useUserCtx(); const { getAccountListHandler } = useAccountingCtx(); const { messageModalDataHandler, messageModalVisibilityHandler } = useModalContext(); @@ -64,35 +53,6 @@ const NewVoucherForm: React.FC = () => { credit: 0, }; - // ToDo: (20241004 - Julian) dummy data - const dummyCounterparty: ICounterparty[] = [ - { - id: 1, - code: '59382730', - name: 'Padberg LLC', - }, - { - id: 2, - code: '59382731', - name: 'Hermiston - West', - }, - { - id: 3, - code: '59382732', - name: 'Feil, Ortiz and Lebsack', - }, - { - id: 4, - code: '59382733', - name: 'Romaguera Inc', - }, - { - id: 5, - code: '59382734', - name: 'Stamm - Baumbach', - }, - ]; - // Info: (20241004 - Julian) 通用項目 const [date, setDate] = useState(default30DayPeriodInSec); const [type, setType] = useState(VoucherType.EXPENSE); @@ -120,8 +80,8 @@ const NewVoucherForm: React.FC = () => { // Info: (20241009 - Julian) 追加項目 const [isCounterpartyRequired, setIsCounterpartyRequired] = useState(false); - // const [isAssetRequired, setIsAssetRequired] = useState(false); - // const [isReverseRequired, setIsReverseRequired] = useState(false); + const [isAssetRequired, setIsAssetRequired] = useState(false); + const [isReverseRequired, setIsReverseRequired] = useState(false); // Info: (20241004 - Julian) 交易對象相關 state const [counterKeyword, setCounterKeyword] = useState(''); @@ -185,8 +145,21 @@ const NewVoucherForm: React.FC = () => { const accountingNull = lineItems.some((item) => item.account === null); // Info: (20241009 - Julian) 會計科目有應收付帳款時,顯示 Counterparty - const isAPorAR = lineItems.some((item) => - AccountTitlesOfAPandAR.includes(item.account?.name || '')); + const isAPorAR = lineItems.some((item) => { + return AccountCodesOfAPandAR.includes(item.account?.code || ''); + }); + + // Info: (20241009 - Julian) 會計科目有資產時,顯示 Asset + const isAsset = lineItems.some((item) => { + return AccountCodesOfAsset.includes(item.account?.code || ''); + }); + + // Info: (20241004 - Julian) 會計科目有應付帳款且借方有值 || 會計科目有應收帳款且貸方有值,顯示 Reverse + const isReverse = lineItems.some( + (item) => + (item.account?.code === '2170' && item.debit > 0) || // Info: (20241009 - Julian) 應付帳款 + (item.account?.code === '1172' && item.credit > 0) // Info: (20241009 - Julian) 應收帳款 + ); setTotalDebit(debitTotal); setTotalCredit(creditTotal); @@ -194,6 +167,8 @@ const NewVoucherForm: React.FC = () => { setIsAccountingNull(accountingNull); setIsCounterpartyRequired(isAPorAR); + setIsAssetRequired(isAsset); + setIsReverseRequired(isReverse); }, [lineItems]); useEffect(() => { @@ -782,29 +757,18 @@ const NewVoucherForm: React.FC = () => {
{/* Info: (20241009 - Julian) Asset */} -
- {/* Info: (20241009 - Julian) Asset Divider */} -
-
-
- asset_icon -

Asset

-
-
+ {isAssetRequired && ( +
+
- {/* Info: (20241009 - Julian) Asset block */} -
-
-

Empty

-

Please add at least 1 asset!

-
- + )} + {/* Info: (20240926 - Julian) Reverse */} + {isReverseRequired && ( +
+
-
- {/* Info: (20240926 - Julian) voucher line block */} + )} + {/* Info: (20240926 - Julian) Voucher line block */} {voucherLineBlock} {/* Info: (20240926 - Julian) buttons */}
diff --git a/src/components/voucher/reverse_section.tsx b/src/components/voucher/reverse_section.tsx new file mode 100644 index 000000000..e4b7c0b6a --- /dev/null +++ b/src/components/voucher/reverse_section.tsx @@ -0,0 +1,188 @@ +import React, { useState } from 'react'; +import Image from 'next/image'; +import { BsChevronDown } from 'react-icons/bs'; +import { FiTrash2 } from 'react-icons/fi'; +import { LuPlus } from 'react-icons/lu'; +import useOuterClick from '@/lib/hooks/use_outer_click'; +import { inputStyle } from '@/constants/display'; +import { IVoucherBeta, dummyVoucherList } from '@/interfaces/voucher'; +import { numberWithCommas } from '@/lib/utils/common'; +import { Button } from '@/components/button/button'; + +interface IReverseSectionProps { + // ToDo: (20241009 - Julian) 未選擇 + isShowReverseVoucherHint?: boolean; +} + +const ReverseLine: React.FC = ({ isShowReverseVoucherHint = false }) => { + const [selectedVoucher, setSelectedVoucher] = useState(null); + const [reverseAmountInput, setReverseAmountInput] = useState(''); + // ToDo: (20241009 - Julian) Send reverse amount to backend + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [reverseAmount, setReverseAmount] = useState(0); + + // Info: (20241009 - Julian) 選單顯示狀態 + const { + targetRef: voucherMenuRef, + componentVisible: isVoucherMenuVisible, + setComponentVisible: setVoucherMenuVisible, + } = useOuterClick(false); + + const toggleVoucherEditing = () => { + setVoucherMenuVisible(!isVoucherMenuVisible); + }; + + const changeReverseAmount = (e: React.ChangeEvent) => { + // Info: (20241009 - Julian) 限制只能輸入數字,並去掉開頭 0 + const value = e.target.value.replace(/\D/g, '').replace(/^0+/, ''); + // Info: (20241009 - Julian) 加入千分位逗號 + setReverseAmountInput(numberWithCommas(value)); + // Info: (20241009 - Julian) 設定 reverseAmount + setReverseAmount(Number(e.target.value)); + }; + + const displayedReverseVoucher = selectedVoucher ? ( +
+
+ {selectedVoucher.voucherNo} +
+
{selectedVoucher.counterparty.name}
+
+ {/* ToDo: (20241009 - Julian) 須確認這欄的內容 */} + {numberWithCommas(selectedVoucher.debit.reduce((acc, cur) => acc + cur, 0))}{' '} + TWD +
+
+ ) : ( +

+ Please select... +

+ ); + + const voucherDropdownMenu = isVoucherMenuVisible ? ( +
+ {dummyVoucherList.map((voucher) => { + const clickHandler = () => { + setSelectedVoucher(voucher); + setVoucherMenuVisible(false); + }; + + return ( +
+
{voucher.voucherNo}
+
{voucher.counterparty.name}
+
+ {/* ToDo: (20241009 - Julian) 須確認這欄的內容 */} + {numberWithCommas(voucher.debit.reduce((acc, cur) => acc + cur, 0))}{' '} + TWD +
+
+ ); + })} +
+ ) : null; + + return ( +
+ {/* Info: (20241009 - Julian) reverse voucher */} +
+

+ Reverse Voucher + * +

+
+ {displayedReverseVoucher} +
+ +
+ {voucherDropdownMenu} +
+
+ {/* Info: (20241009 - Julian) reverse amount */} +
+

+ Reverse Amount + * +

+
+ +
+ tw_icon +

TWD

+
+
+
+ {/* Info: (20241009 - Julian) delete button */} + +
+ ); +}; + +const ReverseSection: React.FC = () => { + const [reverseLineItems, setReverseLineItems] = useState([1]); + + // ToDo: (20241009 - Julian) Implement addReverseLineItem + const addReverseLineItem = () => { + setReverseLineItems([...reverseLineItems, reverseLineItems.length + 1]); + }; + + // ToDo: (20241009 - Julian) 🔧施工中 + // eslint-disable-next-line react/no-array-index-key + const displayedReverseLineItems = reverseLineItems.map((_, index) => ); + + return ( + <> + {/* Info: (20241009 - Julian) Reverse Divider */} +
+
+
+ bell_icon +

Reverse

+
+
+
+ +
+ {/* Info: (20241009 - Julian) reverse voucher list */} +
{displayedReverseLineItems}
+ +
+ + ); +}; + +export default ReverseSection; diff --git a/src/components/voucher/voucher_line_item.tsx b/src/components/voucher/voucher_line_item.tsx index 4130156f8..9990206c2 100644 --- a/src/components/voucher/voucher_line_item.tsx +++ b/src/components/voucher/voucher_line_item.tsx @@ -152,8 +152,8 @@ const VoucherLineItem: React.FC = ({ }; const debitInputChangeHandler = (e: React.ChangeEvent) => { - // Info: (20241001 - Julian) 限制只能輸入數字 - const debitValue = e.target.value.replace(/\D/g, ''); + // Info: (20241001 - Julian) 限制只能輸入數字,並去掉開頭 0 + const debitValue = e.target.value.replace(/\D/g, '').replace(/^0+/, ''); // Info: (20241001 - Julian) 加入千分位逗號 setDebitInput(numberWithCommas(debitValue)); // Info: (20241001 - Julian) 設定 Debit @@ -161,8 +161,8 @@ const VoucherLineItem: React.FC = ({ }; const creditInputChangeHandler = (e: React.ChangeEvent) => { - // Info: (20241001 - Julian) 限制只能輸入數字 - const creditValue = e.target.value.replace(/\D/g, ''); + // Info: (20241001 - Julian) 限制只能輸入數字,並去掉開頭 0 + const creditValue = e.target.value.replace(/\D/g, '').replace(/^0+/, ''); // Info: (20241001 - Julian) 加入千分位逗號 setCreditInput(numberWithCommas(creditValue)); // Info: (20241001 - Julian) 設定 Credit diff --git a/src/constants/asset.ts b/src/constants/asset.ts index adcec1906..940c34be4 100644 --- a/src/constants/asset.ts +++ b/src/constants/asset.ts @@ -5,3 +5,67 @@ export enum AssetStatus { MISSING = 'missing', NORMAL = 'normal', } + +export const AccountCodesOfAsset = [ + '1602', // 土地成本 + '1606', // 土地改良物成本 + '1611', // 房屋及建築成本 + '1616', // 機器設備成本 + '1621', // 裝卸設備成本 + '1626', // 倉儲設備成本 + '1631', // 售氣及輸氣設備成本 + '1636', // 模具設備成本 + '1641', // 水電設備成本 + '1646', // 冷凍設備成本 + '1651', // 漁船設備成本 + '1656', // 電腦通訊設備成本 + '1661', // 試驗設備成本 + '1666', // 污染防治設備成本 + '1671', // 運輸設備成本 + '1676', // 船舶設備成本 + '1681', // 碼頭設備成本 + '1686', // 飛航設備成本 + '1691', // 辦公設備成本 + '1696', // 營業器具成本(觀光飯店業適用) + '1701', // 機具設備成本 + '1706', // 遊樂設備成本 + '1711', // 景觀園藝成本 + '1716', // 租賃資產成本 + '1721', // 出租資產-機器設備成本 + '1726', // 出租資產-其他成本 + '1731', // 租賃改良成本 + '1736', // 其他設備成本 + '1740', // 未完工程及待驗設備 + '1746', // 生產性植物成本 + '1751', // 礦產資源成本 + '17AA', // 使用權資產-土地成本 + '17BA', // 使用權資產-土地改良物成本 + '17CA', // 使用權資產-房屋及建築成本 + '17DA', // 使用權資產-機器設備成本 + '17EA', // 使用權資產-運輸設備成本 + '17FA', // 使用權資產-辦公設備成本 + '17GA', // 使用權資產-其他資產成本 + '1761', // 投資性不動產-土地 + '1765', // 投資性不動產-建築物 + '1771', // 投資性不動產-租賃權益 + '1776', // 投資性不動產-使用權資產 + '1773', // 建造中之投資性不動產 + '1782', // 商標權 + '1786', // 專利權 + '1792', // 特許權 + '1796', // 著作權 + '1802', // 電腦軟體 + '1805', // 商譽 + '1812', // 專門技術 + '1822', // 其他無形資產 + '1826', // 發展中之無形資產 + '1831', // 消耗性生物資產-非流動 + '1835', // 生產性生物資產-非流動 + '1906', // 有形探勘及評估資產成本 + '1911', // 無形探勘及評估資產成本 +]; + +export const AccountCodesOfAPandAR = [ + '1172', // 應收帳款 + '2170', // 應付帳款 +]; diff --git a/src/constants/display.ts b/src/constants/display.ts index 2520f6a30..e748997e2 100644 --- a/src/constants/display.ts +++ b/src/constants/display.ts @@ -66,6 +66,14 @@ export const default30DayPeriodInSec = { export const MILLISECONDS_IN_A_SECOND = 1000; +// Info: (20241009 - Julian) input CSS style +export const inputStyle = { + NORMAL: + 'border-input-stroke-input text-input-text-input-filled placeholder:text-input-text-input-placeholder disabled:text-input-text-input-placeholder', + ERROR: + 'border-input-text-error text-input-text-error placeholder:text-input-text-error disabled:text-input-text-error', +}; + // Info: (20240429 - Julian) checkbox CSS style export const checkboxStyle = 'relative h-16px w-16px appearance-none rounded-xxs border border-checkbox-stroke-unselected bg-white outline-none after:absolute after:top-0 after:-mt-3px after:ml-px after:hidden after:text-sm after:text-checkbox-stroke-check-mark after:content-checked checked:bg-checkbox-surface-selected checked:after:block'; diff --git a/src/interfaces/counterparty.ts b/src/interfaces/counterparty.ts new file mode 100644 index 000000000..d159225a8 --- /dev/null +++ b/src/interfaces/counterparty.ts @@ -0,0 +1,34 @@ +export interface ICounterparty { + id: number; + code: string; + name: string; +} + +// ToDo: (20241004 - Julian) dummy data +export const dummyCounterparty: ICounterparty[] = [ + { + id: 1, + code: '59382730', + name: 'Padberg LLC', + }, + { + id: 2, + code: '59382731', + name: 'Hermiston - West', + }, + { + id: 3, + code: '59382732', + name: 'Feil, Ortiz and Lebsack', + }, + { + id: 4, + code: '59382733', + name: 'Romaguera Inc', + }, + { + id: 5, + code: '59382734', + name: 'Stamm - Baumbach', + }, +]; From d9b3b8c73211ba73a0d35f697c6b2da56eb589fa Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Oct 2024 19:02:04 +0800 Subject: [PATCH 3/5] Feat: translate --- package.json | 2 +- src/components/voucher/asset_section.tsx | 10 ++-- src/components/voucher/new_voucher_form.tsx | 10 +++- src/components/voucher/reverse_section.tsx | 15 +++-- src/constants/asset.ts | 2 +- src/locales/cn/journal.json | 12 ++++ src/locales/en/journal.json | 12 ++++ src/locales/tw/journal.json | 12 ++++ .../users/accounting/add_new_voucher.tsx | 59 +++++++++++++++---- 9 files changed, 107 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 9e140cedf..1394b8f5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+39", + "version": "0.8.2+40", "private": false, "scripts": { "dev": "next dev", diff --git a/src/components/voucher/asset_section.tsx b/src/components/voucher/asset_section.tsx index 0d58ea807..c524ffb33 100644 --- a/src/components/voucher/asset_section.tsx +++ b/src/components/voucher/asset_section.tsx @@ -1,10 +1,12 @@ import React, { useState } from 'react'; import Image from 'next/image'; +import { useTranslation } from 'next-i18next'; import { FiEdit, FiPlus, FiTrash2 } from 'react-icons/fi'; import { Button } from '@/components/button/button'; import { IAssetItem, mockAssetItem } from '@/interfaces/asset'; const AssetSection: React.FC = () => { + const { t } = useTranslation('common'); const [assets, setAssets] = useState([]); // ToDo: (20241009 - Julian) Replace with real function to add asset @@ -63,8 +65,8 @@ const AssetSection: React.FC = () => { }) ) : (
-

Empty

-

Please add at least 1 asset!

+

{t('common:COMMON.EMPTY')}

+

{t('journal:ASSET_SECTION.EMPTY_HINT')}

); @@ -75,7 +77,7 @@ const AssetSection: React.FC = () => {
asset_icon -

Asset

+

{t('journal:ASSET_SECTION.TITLE')}


@@ -84,7 +86,7 @@ const AssetSection: React.FC = () => { {displayedAssetList}
diff --git a/src/components/voucher/new_voucher_form.tsx b/src/components/voucher/new_voucher_form.tsx index 668afd198..40c493dea 100644 --- a/src/components/voucher/new_voucher_form.tsx +++ b/src/components/voucher/new_voucher_form.tsx @@ -279,11 +279,15 @@ const NewVoucherForm: React.FC = () => { setRecurringMenuOpen(!isRecurringMenuOpen); }; - // ToDo: (20240926 - Julian) type 字串轉換 + // Info: (20240926 - Julian) type 字串轉換 const translateType = (voucherType: string) => { return t(`journal:ADD_NEW_VOUCHER.TYPE_${voucherType.toUpperCase()}`); }; + const translateUnit = (unit: RecurringUnit) => { + return t(`common:COMMON.${unit.toUpperCase()}`); + }; + // Info: (20241004 - Julian) 清空表單 const clearAllHandler = () => { setDate(default30DayPeriodInSec); @@ -573,7 +577,7 @@ const NewVoucherForm: React.FC = () => { className="py-8px hover:bg-dropdown-surface-menu-background-secondary" onClick={recurringUnitClickHandler} > - {t(`common:COMMON.${unit.toUpperCase()}`)} + {translateUnit(unit)} ); })} @@ -742,7 +746,7 @@ const NewVoucherForm: React.FC = () => { onClick={recurringUnitToggleHandler} className="relative flex flex-1 items-center justify-between px-12px py-10px text-input-text-input-filled hover:cursor-pointer" > -

{recurringUnit}

+

{translateUnit(recurringUnit)}

{/* Info: (20240926 - Julian) recurring unit dropdown */} {recurringUnitMenu} diff --git a/src/components/voucher/reverse_section.tsx b/src/components/voucher/reverse_section.tsx index e4b7c0b6a..85e03cd00 100644 --- a/src/components/voucher/reverse_section.tsx +++ b/src/components/voucher/reverse_section.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import Image from 'next/image'; +import { useTranslation } from 'next-i18next'; import { BsChevronDown } from 'react-icons/bs'; import { FiTrash2 } from 'react-icons/fi'; import { LuPlus } from 'react-icons/lu'; @@ -15,6 +16,8 @@ interface IReverseSectionProps { } const ReverseLine: React.FC = ({ isShowReverseVoucherHint = false }) => { + const { t } = useTranslation('common'); + const [selectedVoucher, setSelectedVoucher] = useState(null); const [reverseAmountInput, setReverseAmountInput] = useState(''); // ToDo: (20241009 - Julian) Send reverse amount to backend @@ -57,7 +60,7 @@ const ReverseLine: React.FC = ({ isShowReverseVoucherHint

- Please select... + {t('journal:REVERSE_SECTION.REVERSE_VOUCHER_PLACEHOLDER')}

); @@ -96,7 +99,7 @@ const ReverseLine: React.FC = ({ isShowReverseVoucherHint {/* Info: (20241009 - Julian) reverse voucher */}

- Reverse Voucher + {t('journal:REVERSE_SECTION.REVERSE_VOUCHER')} *

= ({ isShowReverseVoucherHint {/* Info: (20241009 - Julian) reverse amount */}

- Reverse Amount + {t('journal:REVERSE_SECTION.REVERSE_AMOUNT')} *

@@ -146,6 +149,8 @@ const ReverseLine: React.FC = ({ isShowReverseVoucherHint }; const ReverseSection: React.FC = () => { + const { t } = useTranslation('common'); + const [reverseLineItems, setReverseLineItems] = useState([1]); // ToDo: (20241009 - Julian) Implement addReverseLineItem @@ -164,7 +169,7 @@ const ReverseSection: React.FC = () => {
bell_icon -

Reverse

+

{t('journal:REVERSE_SECTION.TITLE')}


@@ -178,7 +183,7 @@ const ReverseSection: React.FC = () => { onClick={addReverseLineItem} > -

Add more reverse voucher

+

{t('journal:REVERSE_SECTION.ADD_BTN')}

diff --git a/src/constants/asset.ts b/src/constants/asset.ts index 940c34be4..77b5c7f57 100644 --- a/src/constants/asset.ts +++ b/src/constants/asset.ts @@ -67,5 +67,5 @@ export const AccountCodesOfAsset = [ export const AccountCodesOfAPandAR = [ '1172', // 應收帳款 - '2170', // 應付帳款 + '2171', // 應付帳款 ]; diff --git a/src/locales/cn/journal.json b/src/locales/cn/journal.json index 00fdb8489..17cc25fee 100644 --- a/src/locales/cn/journal.json +++ b/src/locales/cn/journal.json @@ -246,5 +246,17 @@ "INCOME": "收益", "EXPENSE": "支出", "GAIN_OR_LOSS": "收益或损失" + }, + "ASSET_SECTION": { + "TITLE": "资产", + "EMPTY_HINT": "请新增至少 1 笔资产!", + "ADD_BTN": "新增资产" + }, + "REVERSE_SECTION": { + "TITLE": "冲销", + "REVERSE_VOUCHER": "冲销传票", + "REVERSE_VOUCHER_PLACEHOLDER": "请选择...", + "REVERSE_AMOUNT": "冲销金额", + "ADD_BTN": "新增更多冲销传票" } } diff --git a/src/locales/en/journal.json b/src/locales/en/journal.json index e1eaff797..5166f8baf 100644 --- a/src/locales/en/journal.json +++ b/src/locales/en/journal.json @@ -246,5 +246,17 @@ "INCOME": "Income", "EXPENSE": "Expenses", "GAIN_OR_LOSS": "Gain or Loss" + }, + "ASSET_SECTION": { + "TITLE": "Asset", + "EMPTY_HINT": "Please add at least 1 asset!", + "ADD_BTN": "Add New Asset" + }, + "REVERSE_SECTION": { + "TITLE": "Reverse", + "REVERSE_VOUCHER": "Reverse Voucher", + "REVERSE_VOUCHER_PLACEHOLDER": "Please select...", + "REVERSE_AMOUNT": "Reverse Amount", + "ADD_BTN": "Add more reverse voucher" } } diff --git a/src/locales/tw/journal.json b/src/locales/tw/journal.json index 4fe1abfb6..156703e06 100644 --- a/src/locales/tw/journal.json +++ b/src/locales/tw/journal.json @@ -246,5 +246,17 @@ "INCOME": "收益", "EXPENSE": "支出", "GAIN_OR_LOSS": "收益或損失" + }, + "ASSET_SECTION": { + "TITLE": "資產", + "EMPTY_HINT": "請新增至少 1 筆資產!", + "ADD_BTN": "新增資產" + }, + "REVERSE_SECTION": { + "TITLE": "沖銷", + "REVERSE_VOUCHER": "沖銷傳票", + "REVERSE_VOUCHER_PLACEHOLDER": "請選擇...", + "REVERSE_AMOUNT": "沖銷金額", + "ADD_BTN": "新增更多沖銷傳票" } } diff --git a/src/pages/users/accounting/add_new_voucher.tsx b/src/pages/users/accounting/add_new_voucher.tsx index 36dcb6a8e..f0a85c5ab 100644 --- a/src/pages/users/accounting/add_new_voucher.tsx +++ b/src/pages/users/accounting/add_new_voucher.tsx @@ -5,10 +5,37 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { ILocale } from '@/interfaces/locale'; import NewVoucherForm from '@/components/voucher/new_voucher_form'; +// Info: (20241009 - Julian) For layout testing, to be removed +enum SidebarState { + COLLAPSED = 'collapsed', + OPEN = 'open', + EXPANDED = 'expanded', +} + const AddNewVoucherPage: React.FC = () => { const { t } = useTranslation('common'); - const [isSidebarOpen, setIsSidebarOpen] = useState(true); + const [sidebarState, setSidebarState] = useState(SidebarState.OPEN); + + const isSidebarCollapsed = sidebarState === SidebarState.COLLAPSED; + const isSidebarOpen = sidebarState === SidebarState.OPEN; + const isSidebarExpanded = sidebarState === SidebarState.EXPANDED; + + const toggleSidebar = () => { + if (isSidebarCollapsed) { + setSidebarState(SidebarState.OPEN); + } else { + setSidebarState(SidebarState.COLLAPSED); + } + }; + + const expandSidebar = () => { + if (isSidebarOpen) { + setSidebarState(SidebarState.EXPANDED); + } else { + setSidebarState(SidebarState.OPEN); + } + }; return ( <> @@ -19,28 +46,34 @@ const AddNewVoucherPage: React.FC = () => { {t('journal:ADD_NEW_VOUCHER.PAGE_TITLE')} - iSunFA - -
- This is header +
+
+ This is sidebar + +
+
+ Expand Area +
- This is sidebar + This is header
{/* Info: (20240925 - Julian) Body */}
From 1326bee55b7a810a4b8d0fdde924dd50201ea93a Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Wed, 9 Oct 2024 19:38:59 +0800 Subject: [PATCH 4/5] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1394b8f5d..0a042d616 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+40", + "version": "0.8.2+41", "private": false, "scripts": { "dev": "next dev", From ba3b599e380615d9161efff1b052a57cd9a570b1 Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Wed, 9 Oct 2024 19:45:18 +0800 Subject: [PATCH 5/5] Update reverse_section.tsx --- src/components/voucher/reverse_section.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/voucher/reverse_section.tsx b/src/components/voucher/reverse_section.tsx index 85e03cd00..aafa8e9d7 100644 --- a/src/components/voucher/reverse_section.tsx +++ b/src/components/voucher/reverse_section.tsx @@ -21,6 +21,7 @@ const ReverseLine: React.FC = ({ isShowReverseVoucherHint const [selectedVoucher, setSelectedVoucher] = useState(null); const [reverseAmountInput, setReverseAmountInput] = useState(''); // ToDo: (20241009 - Julian) Send reverse amount to backend + // Deprecated: (20241009 - Julian) code incomplete // eslint-disable-next-line @typescript-eslint/no-unused-vars const [reverseAmount, setReverseAmount] = useState(0); @@ -159,6 +160,7 @@ const ReverseSection: React.FC = () => { }; // ToDo: (20241009 - Julian) 🔧施工中 + // Deprecated: (20241009 - Julian) code incomplete // eslint-disable-next-line react/no-array-index-key const displayedReverseLineItems = reverseLineItems.map((_, index) => );