Skip to content

Commit

Permalink
Feat: develop new voucher form
Browse files Browse the repository at this point in the history
  • Loading branch information
Julian0701 committed Sep 26, 2024
1 parent 48402df commit 5b13695
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "iSunFA",
"version": "0.8.2+19",
"version": "0.8.2+20",
"private": false,
"scripts": {
"dev": "next dev",
Expand Down
170 changes: 170 additions & 0 deletions src/components/voucher/new_voucher_form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React, { useState } from 'react';
import { FaChevronDown } from 'react-icons/fa6';
import { BiSave } from 'react-icons/bi';
import { useTranslation } from 'next-i18next';
import useOuterClick from '@/lib/hooks/use_outer_click';
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 { default30DayPeriodInSec } from '@/constants/display';
import { VoucherType } from '@/constants/account';

const NewVoucherForm = () => {
const { t } = useTranslation('common');

const [date, setDate] = useState<IDatePeriod>(default30DayPeriodInSec);
const [type, setType] = useState<string>(VoucherType.EXPENSE);
const [note, setNote] = useState<string>('');
const [counterparty, setCounterparty] = useState<string>('');
const [isRecurring, setIsRecurring] = useState<boolean>(false);

// ToDo: (20240926 - Julian) Add 'credit not equal to debit'
const saveBtnDisabled = (date.startTimeStamp === 0 && date.endTimeStamp === 0) || type === '';

const {
targetRef: typeRef,
componentVisible: typeVisible,
setComponentVisible: setTypeVisible,
} = useOuterClick<HTMLDivElement>(false);

const typeToggleHandler = () => {
setTypeVisible(!typeVisible);
};

const noteChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setNote(e.target.value);
};

const counterpartyChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setCounterparty(e.target.value);
};

const recurringToggleHandler = () => {
setIsRecurring(!isRecurring);
};

// ToDo: (20240926 - Julian) Save voucher function
const saveVoucher = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};

// ToDo: (20240926 - Julian) type 字串轉換
const translateType = (voucherType: string) => {
return t(`journal:ADD_NEW_VOUCHER.TYPE_${voucherType.toUpperCase()}`);
};

const typeDropdownMenu = typeVisible ? (
<div
ref={typeRef}
className="absolute left-0 top-50px flex w-full flex-col rounded-sm border border-dropdown-stroke-menu bg-dropdown-surface-menu-background-primary p-8px text-dropdown-text-primary shadow-dropmenu"
>
{Object.values(VoucherType).map((voucherType) => {
const typeClickHandler = () => {
setType(voucherType);
setTypeVisible(false);
};

return (
<button
key={voucherType}
id={`type-${voucherType}`}
type="button"
className="px-12px py-8px text-left hover:bg-dropdown-surface-item-hover"
onClick={typeClickHandler}
>
{translateType(voucherType)}
</button>
);
})}
</div>
) : null;

return (
<div className="relative flex flex-col items-center gap-40px px-40px py-40px">
{/* ToDo: (20240926 - Julian) AI analyze */}
<div className="w-full bg-surface-brand-primary-moderate p-40px text-center text-white">
This is AI analyze
</div>
{/* ToDo: (20240926 - Julian) Uploaded certificates */}
<div className="w-full bg-stroke-neutral-quaternary p-40px text-center text-white">
Uploaded certificates
</div>

{/* Info: (20240926 - Julian) form */}
<form onSubmit={saveVoucher} className="grid w-full grid-cols-2 gap-24px">
{/* Info: (20240926 - Julian) Date */}
<div className="flex flex-col gap-8px">
<p className="font-bold text-input-text-primary">
{t('journal:ADD_NEW_VOUCHER.VOUCHER_DATE')}
<span className="text-text-state-error">*</span>
</p>
<DatePicker type={DatePickerType.TEXT_DATE} period={date} setFilteredPeriod={setDate} />
</div>
{/* Info: (20240926 - Julian) Type */}
<div className="flex flex-col gap-8px">
<p className="font-bold text-input-text-primary">
{t('journal:ADD_NEW_VOUCHER.VOUCHER_TYPE')}
<span className="text-text-state-error">*</span>
</p>
<div
onClick={typeToggleHandler}
className="relative flex items-center justify-between rounded-sm border border-input-stroke-input bg-input-surface-input-background px-12px py-10px hover:cursor-pointer"
>
<p className="text-base text-input-text-input-filled">{translateType(type)}</p>
<FaChevronDown size={20} />
{/* Info: (20240926 - Julian) Type dropdown */}
{typeDropdownMenu}
</div>
</div>
{/* Info: (20240926 - Julian) Note */}
<div className="col-span-2 flex flex-col gap-8px">
<p className="font-bold text-input-text-primary">{t('journal:ADD_NEW_VOUCHER.NOTE')}</p>
<input
id="note-input"
type="text"
value={note}
onChange={noteChangeHandler}
placeholder={t('journal:ADD_NEW_VOUCHER.NOTE')}
className="rounded-sm border border-input-stroke-input px-12px py-10px outline-none placeholder:text-input-text-input-placeholder"
/>
</div>
{/* Info: (20240926 - Julian) Counterparty */}
<div className="col-span-2 flex flex-col gap-8px">
<p className="font-bold text-input-text-primary">
{t('journal:ADD_NEW_VOUCHER.COUNTERPARTY')}
</p>
<input
id="counterparty-input"
type="text"
value={counterparty}
onChange={counterpartyChangeHandler}
placeholder={t('journal:ADD_NEW_VOUCHER.COUNTERPARTY')}
className="rounded-sm border border-input-stroke-input px-12px py-10px outline-none placeholder:text-input-text-input-placeholder"
/>
</div>
{/* Info: (20240926 - Julian) switch */}
<div className="col-span-2 flex items-center gap-16px text-switch-text-primary">
<Toggle id="recurring-toggle" getToggledState={recurringToggleHandler} />
<p>{t('journal:ADD_NEW_VOUCHER.RECURRING_ENTRY')}</p>
</div>
{/* ToDo: (20240926 - Julian) voucher block */}
<div className="col-span-2 w-full bg-surface-brand-secondary-moderate p-40px text-center text-white">
This is voucher block
</div>
{/* Info: (20240926 - Julian) buttons */}
<div className="col-span-2 ml-auto flex items-center gap-12px">
<Button type="button" variant="secondaryOutline">
{t('journal:JOURNAL.CLEAR_ALL')}
</Button>
<Button type="submit" disabled={saveBtnDisabled}>
<p>{t('common:COMMON.SAVE')}</p>
<BiSave size={20} />
</Button>
</div>
</form>
</div>
);
};

export default NewVoucherForm;
11 changes: 11 additions & 0 deletions src/locales/cn/journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,16 @@
"EXPORT_BTN": "汇出",
"FROM_PLACEHOLDER": "",
"TO_PLACEHOLDER": ""
},
"ADD_NEW_VOUCHER": {
"PAGE_TITLE": "新增传票",
"VOUCHER_DATE": "传票日期",
"VOUCHER_TYPE": "传票类型",
"TYPE_RECEIVE": "收入",
"TYPE_EXPENSE": "支付",
"TYPE_TRANSFER": "转帐",
"NOTE": "备注",
"COUNTERPARTY": "交易对象",
"RECURRING_ENTRY": "周期性分录"
}
}
11 changes: 11 additions & 0 deletions src/locales/en/journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,16 @@
"EXPORT_BTN": "Export",
"FROM_PLACEHOLDER": "From",
"TO_PLACEHOLDER": "To"
},
"ADD_NEW_VOUCHER": {
"PAGE_TITLE": "Add New Voucher",
"VOUCHER_DATE": "Voucher Date",
"VOUCHER_TYPE": "Voucher Type",
"TYPE_RECEIVE": "Receiving",
"TYPE_EXPENSE": "Payment",
"TYPE_TRANSFER": "Transfer",
"NOTE": "Note",
"COUNTERPARTY": "Counterparty",
"RECURRING_ENTRY": "Recurring Entry"
}
}
11 changes: 11 additions & 0 deletions src/locales/tw/journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,16 @@
"EXPORT_BTN": "匯出",
"FROM_PLACEHOLDER": "",
"TO_PLACEHOLDER": ""
},
"ADD_NEW_VOUCHER": {
"PAGE_TITLE": "新增傳票",
"VOUCHER_DATE": "傳票日期",
"VOUCHER_TYPE": "傳票類型",
"TYPE_RECEIVE": "收入",
"TYPE_EXPENSE": "支付",
"TYPE_TRANSFER": "轉帳",
"NOTE": "備註",
"COUNTERPARTY": "交易對象",
"RECURRING_ENTRY": "週期性分錄"
}
}
70 changes: 70 additions & 0 deletions src/pages/users/accounting/add_new_voucher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { ILocale } from '@/interfaces/locale';
import NewVoucherForm from '@/components/voucher/new_voucher_form';

const AddNewVoucherPage = () => {
const { t } = useTranslation('common');

const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true);

return (
<>
<Head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon/favicon.ico" />
<title>{t('journal:ADD_NEW_VOUCHER.PAGE_TITLE')} - iSunFA</title>
</Head>

<button
type="button"
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="absolute top-0 bg-rose-300"
>
Sidebar Toggle
</button>

<div
className={`${isSidebarOpen ? 'ml-280px' : 'ml-0'} bg-text-neutral-secondary p-20px text-center text-white transition-all duration-300 ease-in-out`}
>
This is header
</div>
<div
className={`fixed flex h-screen overflow-hidden ${isSidebarOpen ? 'w-280px' : 'w-0'} z-50 flex-col items-center justify-center bg-surface-neutral-surface-lv2 transition-all duration-300 ease-in-out`}
>
This is sidebar
</div>

{/* Info: (20240925 - Julian) Body */}
<main
className={`${isSidebarOpen ? 'pl-280px' : 'pl-0'} flex w-screen flex-col overflow-y-auto bg-surface-neutral-main-background font-barlow transition-all duration-300 ease-in-out`}
>
<NewVoucherForm />
</main>
</>
);
};

const getStaticPropsFunction = async ({ locale }: ILocale) => ({
props: {
...(await serverSideTranslations(locale, [
'common',
'journal',
'kyc',
'project',
'report_401',
'salary',
'setting',
'terms',
'asset',
])),
locale,
},
});

export const getStaticProps = getStaticPropsFunction;

export default AddNewVoucherPage;

0 comments on commit 5b13695

Please sign in to comment.