Skip to content

Commit

Permalink
Merge pull request #869 from NUTFes/feat/kubosaka/868-edit-invoice-modal
Browse files Browse the repository at this point in the history
請求書の編集機能の追加
  • Loading branch information
Kubosaka authored Aug 11, 2024
2 parents ef6ee4b + 0327c83 commit 34bad9d
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 103 deletions.
22 changes: 19 additions & 3 deletions view/next-project/src/components/common/EditButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { RiPencilFill } from 'react-icons/ri';
interface Props {
onClick?: () => void;
isDisabled?: boolean;
size?: 'S' | 'M' | 'L';
}

const EditButton: React.FC<Props> = (props) => {
const { onClick, isDisabled = false } = props;
const { onClick, isDisabled = false, size } = props;

const buttonClass = useMemo(() => {
if (isDisabled) {
Expand All @@ -17,17 +18,32 @@ const EditButton: React.FC<Props> = (props) => {
}
}, [isDisabled]);

const iconSize = (): { button: string; icon: string } => {
switch (size) {
case 'S':
return { button: '6', icon: '12' };
case 'M':
return { button: '12', icon: '20' };
case 'L':
return { button: '24', icon: '30' };
default:
return { button: '6', icon: '12' };
}
};

return (
<button
suppressHydrationWarning
className={`${buttonClass} flex h-6 w-6 min-w-0 items-center justify-center rounded-full p-0`}
className={`${buttonClass} flex h-${iconSize().button} w-${
iconSize().button
} min-w-0 items-center justify-center rounded-full p-0`}
disabled={isDisabled}
onClick={(e) => {
if (onClick) onClick();
e.stopPropagation();
}}
>
<RiPencilFill size={'15px'} color={'white'} />
<RiPencilFill size={`${iconSize().icon}px`} color={'white'} />
</button>
);
};
Expand Down
2 changes: 1 addition & 1 deletion view/next-project/src/components/common/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface Props {

function Input(props: Props): JSX.Element {
const className =
'rounded-full border border-primary-1 py-2 px-4' +
'rounded-full border border-primary-1 py-2 px-4 w-full' +
(props.className ? ` ${props.className}` : '');
return (
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { useState } from 'react';

import { OpenEditInvoiceModalButton } from './index';
import { createSponsorActivitiesPDF } from '@/utils/createSponsorActivitiesInvoicesPDF';
import { PreviewPDF } from '@/utils/createSponsorActivitiesInvoicesPDF';
import { CloseButton, Input, Modal, PrimaryButton } from '@components/common';
import { SponsorActivityView } from '@type/common';
import {
SponsorActivityView,
Invoice,
SponsorStyleDetail,
InvoiceSponsorStyle,
} from '@type/common';

interface ModalProps {
setIsOpen: (isOpen: boolean) => void;
Expand All @@ -17,6 +23,7 @@ interface FormDateFormat {
}

export default function AddPdfDetailModal(props: ModalProps) {
const { sponsorActivitiesViewItem } = props;
const today = new Date();
const yyyy = String(today.getFullYear());
const mm = '08';
Expand Down Expand Up @@ -47,22 +54,43 @@ export default function AddPdfDetailModal(props: ModalProps) {
).padStart(2, '0')}`;
};

const [formData, setFormData] = useState<FormDateFormat>({
receivedAt: ymd,
billIssuedAt: todayFormatted(),
const sponsorStyleFormatted = (): InvoiceSponsorStyle[] => {
return sponsorActivitiesViewItem.styleDetail.map((sponsorStyleDetail) => {
const sponsorStyle = sponsorStyleDetail.sponsorStyle;
const res: InvoiceSponsorStyle = {
styleName: `${sponsorStyle.style}(${sponsorStyle.feature})`,
price: sponsorStyle.price,
};
return res;
});
};

const CalculateTotalPrice = () => {
return sponsorActivitiesViewItem.styleDetail.reduce(
(price: number, sponsorStyleDetail: SponsorStyleDetail): number => {
return price + sponsorStyleDetail.sponsorStyle.price;
},
0,
);
};

const [invoiceData, setInvoiceDate] = useState<Invoice>({
sponsorName: sponsorActivitiesViewItem.sponsor.name,
managerName: sponsorActivitiesViewItem.sponsor.representative,
totalPrice: CalculateTotalPrice(),
fesStuffName: sponsorActivitiesViewItem.user.name,
invoiceSponsorStyle: sponsorStyleFormatted(),
issuedDate: todayFormatted(),
deadline: ymd,
remark: '',
});
const [remarks, setRemarks] = useState('');

const handler =
(input: string) =>
(e: React.ChangeEvent<HTMLSelectElement> | React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [input]: e.target.value });
setInvoiceDate({ ...invoiceData, [input]: e.target.value });
};

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

return (
<Modal className='md:w-1/2'>
<div className='w-full'>
Expand All @@ -74,53 +102,52 @@ export default function AddPdfDetailModal(props: ModalProps) {
/>
</div>
<p className='mx-auto mb-7 w-fit text-2xl font-thin leading-8 tracking-widest text-black-600'>
振込締め切り日・備考の入力
請求書の発行
</p>
<div className='col-span-4 w-full'>
<p className='text-gray-600 mb-3 ml-1 text-sm'>請求書発行日</p>
<Input
type='date'
value={formData.billIssuedAt}
onChange={handler('billIssuedAt')}
value={invoiceData.issuedDate}
onChange={handler('issuedDate')}
className='mb-3 w-full'
/>
<p className='text-gray-600 mb-3 ml-1 text-sm'>振込締め切り日</p>
<Input
type='date'
value={formData.receivedAt}
onChange={handler('receivedAt')}
value={invoiceData.deadline}
onChange={handler('deadline')}
className='mb-3 w-full'
/>
<p className='text-gray-600 mb-3 ml-1 text-sm'>備考を入力</p>
<Input
type='text'
value={remarks}
onChange={handleRemarksChange}
value={invoiceData.remark}
onChange={handler('remark')}
className='mb-3 w-full'
/>
</div>
<div className='mb-3 flex w-full justify-center'>
<div className='mb-3 flex w-full justify-center gap-4'>
<PrimaryButton
onClick={async () => {
createSponsorActivitiesPDF(
props.sponsorActivitiesViewItem,
formatDate(formData.receivedAt),
formatDate(formData.billIssuedAt, false),
remarks,
invoiceData,
formatDate(invoiceData.deadline),
formatDate(invoiceData.issuedDate, false),
);
props.setIsOpen(false);
}}
>
ダウンロード
</PrimaryButton>
<OpenEditInvoiceModalButton invoice={invoiceData} setInvoice={setInvoiceDate} />
</div>
</div>
<div className='h-[30rem] justify-center overflow-x-auto md:flex'>
<PreviewPDF
sponsorActivitiesViewItem={props.sponsorActivitiesViewItem}
date={formatDate(formData.receivedAt)}
issuedDate={formatDate(formData.billIssuedAt, false)}
remarks={remarks}
invoiceItem={invoiceData}
deadline={formatDate(invoiceData.deadline)}
issuedDate={formatDate(invoiceData.issuedDate, false)}
/>
</div>
</Modal>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import clsx from 'clsx';
import React, { useState } from 'react';
import { PrimaryButton, OutlinePrimaryButton, CloseButton, Modal, Input } from '@components/common';
import { Invoice, InvoiceSponsorStyle } from '@type/common';

interface ModalProps {
invoice: Invoice;
setInvoice: (invoce: Invoice) => void;
setIsOpen: (isOpen: boolean) => void;
}

export default function EditInvoiceModal(props: ModalProps) {
const { invoice, setInvoice, setIsOpen } = props;
const [editInvoice, setEditInvoice] = useState<Invoice>(invoice);

const handler =
(input: string) =>
(
e:
| React.ChangeEvent<HTMLSelectElement>
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLTextAreaElement>,
) => {
setEditInvoice({ ...editInvoice, [input]: e.target.value });
};

const onChangeSponsorStyle = (inputInvoiceSponsorStyle: InvoiceSponsorStyle, index: number) => {
const newInvoiceSponsorStyles = editInvoice.invoiceSponsorStyle.map(
(invoiceSponsorStyle, i) => {
if (i === index) {
return inputInvoiceSponsorStyle;
} else {
return invoiceSponsorStyle;
}
},
);
const totalPrice = newInvoiceSponsorStyles.reduce(
(price: number, invoiceSponsorStyle: InvoiceSponsorStyle): number => {
return price + invoiceSponsorStyle.price;
},
0,
);
setEditInvoice({
...editInvoice,
invoiceSponsorStyle: newInvoiceSponsorStyles,
totalPrice: totalPrice,
});
};

const handleRegister = () => {
setInvoice(editInvoice);
setIsOpen(false);
};

return (
<Modal className='md:w-1/2'>
<div className='w-full'>
<div className='ml-auto w-fit'>
<CloseButton
onClick={() => {
setIsOpen(false);
}}
/>
</div>
</div>
<div className='mx-auto mb-10 w-fit text-xl text-black-600'>請求書の修正</div>
<div className=''>
<div className='my-4 grid grid-cols-5 items-center justify-items-center gap-2'>
<p className='text-black-600'>企業名</p>
<div className='col-span-4 w-full'>
<Input value={editInvoice.sponsorName} onChange={handler('sponsorName')}></Input>
</div>
<p className='text-black-600'>企業担当者名</p>
<div className='col-span-4 w-full'>
<Input value={editInvoice.managerName} onChange={handler('managerName')}></Input>
</div>
<p className='text-black-600'>実行委員担当者名</p>
<div className='col-span-4 w-full'>
<Input value={editInvoice.fesStuffName} onChange={handler('fesStuffName')}></Input>
</div>
</div>
<div className='max-h-48 overflow-y-auto'>
<table className='mb-4 w-full table-fixed border-collapse'>
<thead>
<tr className='border border-x-white-0 border-b-primary-1 border-t-white-0 py-3'>
<th className='w-3/4 px-6 pb-2'>
<div className='text-center text-sm text-black-600'>協賛内容(オプション)</div>
</th>
<th className='w-1/4 px-6 pb-2'>
<div className='text-center text-sm text-black-600'>値段</div>
</th>
</tr>
</thead>
<tbody>
{editInvoice.invoiceSponsorStyle &&
editInvoice.invoiceSponsorStyle.map((invoiceSponsorStyle, index) => (
<tr
key={index}
className={clsx('border border-x-white-0 border-t-white-0', {
'border-b-primary-1': index === editInvoice.invoiceSponsorStyle.length - 1,
})}
>
<td className='py-3'>
<Input
value={invoiceSponsorStyle.styleName}
className=''
onChange={(e) => {
onChangeSponsorStyle(
{ ...invoiceSponsorStyle, styleName: e.target.value },
index,
);
}}
></Input>
</td>
<td className='py-3'>
<Input
value={invoiceSponsorStyle.price}
onChange={(e) => {
onChangeSponsorStyle(
{
...invoiceSponsorStyle,
price: isNaN(Number(e.target.value))
? invoiceSponsorStyle.price
: Number(e.target.value),
},
index,
);
}}
></Input>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className='flex flex-row justify-center gap-5'>
<OutlinePrimaryButton
onClick={() => {
setIsOpen(false);
}}
>
戻る
</OutlinePrimaryButton>
<PrimaryButton onClick={handleRegister}>編集完了</PrimaryButton>
</div>
</div>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import { useState } from 'react';

import { EditButton } from '../common';
import EditInvoiceModal from './EditInvoiceModal';
import { Invoice } from '@/type/common';

interface Props {
children?: React.ReactNode;
invoice: Invoice;
setInvoice: (invoice: Invoice) => void;
}

const OpenEditInvoiceModalButton: React.FC<Props> = (props) => {
const { invoice, setInvoice } = props;
const [isOpen, setIsOpen] = useState(false);
const onOpen = () => {
setIsOpen(true);
};
return (
<>
<EditButton onClick={onOpen} size='M' />
{isOpen && (
<EditInvoiceModal setInvoice={setInvoice} invoice={invoice} setIsOpen={setIsOpen} />
)}
</>
);
};

export default OpenEditInvoiceModalButton;
2 changes: 2 additions & 0 deletions view/next-project/src/components/sponsoractivities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export { default as OpenPaymentDayModalButton } from './OpenPaymentDayModalButto
export { default as PaymentDayModal } from './PaymentDayModal';
export { default as SponsorActivitiesAddModal } from './SponsorActivitiesAddModal';
export { default as UploadFileModal } from './UploadFileModal';
export { default as EditInvoiceModal } from './EditInvoiceModal';
export { default as OpenEditInvoiceModalButton } from './OpenEditInvoiceModalButton';
Loading

0 comments on commit 34bad9d

Please sign in to comment.