Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

請求書の編集機能の追加 #869

Merged
merged 4 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading