import { useEffect, useMemo } from 'react'; import * as Yup from 'yup'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; // @mui import LoadingButton from '@mui/lab/LoadingButton'; import Card from '@mui/material/Card'; import Stack from '@mui/material/Stack'; // routes import { paths } from 'src/routes/paths'; import { useRouter } from 'src/routes/hooks'; // types import { Invoice } from 'mvpmasters-shared'; // hooks import { useBoolean } from 'src/hooks/use-boolean'; // components import FormProvider from 'src/components/hook-form'; // import { incrementInvoiceNumber } from 'src/utils/increment-invoice-number'; import uploadToFirebaseStorage from 'src/utils/upload-to-firebase-storage'; import { pdf } from '@react-pdf/renderer'; import { useGetSettings } from 'src/api/settings'; import { collections, documents, firestoreBatch, generateId, updateDocument, } from 'src/lib/firestore'; import { useGetServices } from 'src/api/service'; import { mutate } from 'swr'; import { Timestamp } from 'firebase/firestore'; import InvoiceNewEditStatusDate from './invoice-new-edit-status-date'; import InvoiceNewEditAddress from './invoice-new-edit-address'; import InvoiceNewEditDetails from './invoice-new-edit-details'; import InvoicePDF from './invoice-pdf'; // ---------------------------------------------------------------------- type Props = { isCopy?: boolean; currentInvoice?: Invoice; }; export const monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; export default function InvoiceNewEditForm({ isCopy, currentInvoice }: Props) { const router = useRouter(); const loadingSave = useBoolean(); const loadingSend = useBoolean(); const itemSchema = Yup.object({ title: Yup.string().required('Title is required'), description: Yup.string().required('Description is required'), service: Yup.string().nullable(), quantity: Yup.number().required('Quantity is required').min(0.5, 'Quantity must be at least 1'), price: Yup.number().required('Price is required').min(0, 'Price must be at least 0'), total: Yup.number().required('Total is required').min(0, 'Total must be at least 0'), }); const NewInvoiceSchema = Yup.object().shape({ invoiceNumber: Yup.string().nullable().required('Invoice number is required'), createDate: Yup.mixed().nullable().required('Create date is required'), dueDate: Yup.mixed() .required('Due date is required') .test( 'date-min', 'Due date must be later than create date', (value, { parent }) => value.getTime() > parent.createDate.getTime() ), invoiceFrom: Yup.mixed().nullable().required('Invoice from is required'), invoiceTo: Yup.mixed().nullable().required('Invoice to is required'), currency: Yup.string().oneOf(['EUR', 'USD']).required('Currency is required'), quantityType: Yup.string() .oneOf(['Unit', 'Hour', 'Sprint', 'Month']) .required('Quantity type is required'), month: Yup.string().oneOf(monthNames).required('Month is required'), status: Yup.string().required(), totalAmount: Yup.number().required(), // not required taxes: Yup.number(), discount: Yup.number(), items: Yup.array() .of(itemSchema) .required('At least one item is required') .min(1, 'At least one item must be provided'), }); const { services: invoiceServices } = useGetServices(); const { settings, settingsEmpty, settingsLoading } = useGetSettings(); const defaultValues = useMemo( () => ({ invoiceNumber: !isCopy && currentInvoice?.invoiceNumber ? currentInvoice?.invoiceNumber : incrementInvoiceNumber(settings?.invoice?.lastInvoiceNumber), createDate: currentInvoice?.createDate?.toDate() || new Date(), dueDate: currentInvoice?.dueDate?.toDate() || null, invoiceFrom: currentInvoice?.invoiceFrom || null, invoiceTo: currentInvoice?.invoiceTo || null, currency: currentInvoice?.currency || 'EUR', quantityType: currentInvoice?.quantityType || 'Unit', month: currentInvoice?.month || monthNames[new Date().getMonth()], // not required taxes: currentInvoice?.taxes || 0, status: !isCopy && currentInvoice?.status ? currentInvoice?.status : 'draft', discount: currentInvoice?.discount || 0, items: currentInvoice?.items.map((item) => ({ ...item, service: item.service?.id || null, })) || [ { title: '', description: '', service: '', quantity: 1, price: 0, total: 0, }, ], subTotal: currentInvoice?.subTotal || 0, totalAmount: currentInvoice?.totalAmount || 0, }), [currentInvoice, isCopy, settings] ); const methods = useForm({ resolver: yupResolver(NewInvoiceSchema), defaultValues, }); const { reset, handleSubmit, formState: { isSubmitting }, } = methods; useEffect(() => { if (!settingsEmpty && !settingsLoading) { // eslint-disable-next-line react-hooks/exhaustive-deps reset(defaultValues); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [settingsEmpty, settingsLoading]); const handleSaveAsDraft = handleSubmit(async (data) => { loadingSave.onTrue(); try { // generate collection id const id = generateId(collections.invoice); // attach serivce details const items = data.items.map((item) => ({ ...item, service: invoiceServices.find((service) => service.id === item.service) || null, })); const currentTime = new Date(); const createDateWithCurrentTime = new Date(data.createDate); // This creates a date object using the date from data.createDate // Set the time of createDateWithCurrentTime to the current hour, minutes, and seconds createDateWithCurrentTime.setHours( currentTime.getHours(), currentTime.getMinutes(), currentTime.getSeconds() ); // transform data const writeData = { ...data, invoiceNumber: incrementInvoiceNumber(settings?.invoice.lastInvoiceNumber), status: 'draft', createDate: Timestamp.fromDate(createDateWithCurrentTime), dueDate: Timestamp.fromDate(new Date(data.dueDate)), items, }; // upload invoice PDF to storage const invoicePdf = ; const blob: Blob = await pdf(invoicePdf).toBlob(); const storagePath: string = `invoices/${writeData.invoiceTo.name}/${id}-${writeData.invoiceNumber}.pdf`; await uploadToFirebaseStorage(blob, storagePath); // write to DB await firestoreBatch([ { docPath: `${collections.invoice}/${id}`, type: 'set', data: { ...writeData, pdfRef: storagePath, }, }, { docPath: `${collections.settings}/${documents.settingsInvoice}`, type: 'set', data: { lastInvoiceNumber: writeData.invoiceNumber }, }, ]); loadingSave.onFalse(); router.push(paths.dashboard.invoice.root); // console.info('DATA', JSON.stringify(writeData, null, 2)); } catch (error) { console.error(error); loadingSave.onFalse(); } }); const handleCreateAndUpdate = handleSubmit(async (data) => { loadingSend.onTrue(); try { if (currentInvoice) { // attach serivce details const items = data.items.map((item) => ({ ...item, service: invoiceServices.find((service) => service.id === item.service) || null, })); // transform data const writeData = { ...data, createDate: Timestamp.fromDate(new Date(data.createDate)), dueDate: Timestamp.fromDate(new Date(data.dueDate)), items, }; // upload invoice PDF to storage const invoicePdf = ; const blob: Blob = await pdf(invoicePdf).toBlob(); const storagePath: string = `invoices/${data.invoiceTo.name}/${currentInvoice.id}-${data.invoiceNumber}.pdf`; await uploadToFirebaseStorage(blob, storagePath); // update DB await updateDocument(collections.invoice, currentInvoice.id, { ...writeData, pdfRef: storagePath, }); // mutate current data mutate([collections.invoice, currentInvoice.id]); } else { // generate collection id const id = generateId(collections.invoice); // attach serivce details const items = data.items.map((item) => ({ ...item, service: invoiceServices?.find((service) => service.id === item.service) || null, })); // transform data const writeData = { ...data, createDate: Timestamp.fromDate(new Date(data.createDate)), dueDate: Timestamp.fromDate(new Date(data.dueDate)), items, }; // upload invoice PDF to storage const invoicePdf = ; const blob: Blob = await pdf(invoicePdf).toBlob(); const storagePath: string = `invoices/${data.invoiceTo.name}/${id}-${data.invoiceNumber}.pdf`; await uploadToFirebaseStorage(blob, storagePath); // write to DB await firestoreBatch([ { docPath: `${collections.invoice}/${id}`, type: 'set', data: { ...writeData, pdfRef: storagePath, }, }, { docPath: `${collections.settings}/${documents.settingsInvoice}`, type: 'set', data: { lastInvoiceNumber: writeData.invoiceNumber }, }, ]); reset(); } loadingSend.onFalse(); router.push(paths.dashboard.invoice.root); // console.info('DATA', JSON.stringify(data, null, 2)); } catch (error) { console.error(error); loadingSend.onFalse(); } }); return ( {settings?.company && } {currentInvoice && isCopy && ( Save as Draft )} {!isCopy && ( {currentInvoice ? 'Update' : 'Create'} )} ); }