1 | import { useState, useMemo } from 'react';
|
---|
2 | // @mui
|
---|
3 | import Paper from '@mui/material/Paper';
|
---|
4 | import Stack from '@mui/material/Stack';
|
---|
5 | import Portal from '@mui/material/Portal';
|
---|
6 | import Backdrop from '@mui/material/Backdrop';
|
---|
7 | import IconButton from '@mui/material/IconButton';
|
---|
8 | import Typography from '@mui/material/Typography';
|
---|
9 | // hooks
|
---|
10 | import { useBoolean } from 'src/hooks/use-boolean';
|
---|
11 | import { useResponsive } from 'src/hooks/use-responsive';
|
---|
12 | // components
|
---|
13 | import Iconify from 'src/components/iconify';
|
---|
14 | import Editor from 'src/components/editor';
|
---|
15 | import { Invoice } from 'mvpmasters-shared';
|
---|
16 | import FormProvider from 'src/components/hook-form/form-provider';
|
---|
17 | import { RHFTextField } from 'src/components/hook-form';
|
---|
18 | import { useForm } from 'react-hook-form';
|
---|
19 | import { LoadingButton } from '@mui/lab';
|
---|
20 | import { enqueueSnackbar } from 'notistack';
|
---|
21 | import {
|
---|
22 | collectionRef,
|
---|
23 | collections,
|
---|
24 | createDocId,
|
---|
25 | createDocument,
|
---|
26 | firestoreBatch,
|
---|
27 | } from 'src/lib/firestore';
|
---|
28 | import { storage } from 'src/lib/firebase';
|
---|
29 | import { getDownloadURL, ref } from 'firebase/storage';
|
---|
30 | import { increment } from 'firebase/firestore';
|
---|
31 | import { mutate } from 'swr';
|
---|
32 |
|
---|
33 | // ----------------------------------------------------------------------
|
---|
34 |
|
---|
35 | const ZINDEX = 1998;
|
---|
36 |
|
---|
37 | const POSITION = 24;
|
---|
38 |
|
---|
39 | type Props = {
|
---|
40 | invoice: Invoice | null;
|
---|
41 | onCloseCompose: VoidFunction;
|
---|
42 | invoiceMutationKey: string[];
|
---|
43 | };
|
---|
44 |
|
---|
45 | export default function MailCompose({ invoice, onCloseCompose, invoiceMutationKey }: Props) {
|
---|
46 | const smUp = useResponsive('up', 'sm');
|
---|
47 |
|
---|
48 | const fullScreen = useBoolean(false);
|
---|
49 |
|
---|
50 | // const handleChangeMessage = useCallback((value: string) => {
|
---|
51 | // setMessage(value);
|
---|
52 | // }, []);
|
---|
53 |
|
---|
54 | const defaultValues = useMemo(
|
---|
55 | () => ({
|
---|
56 | to: invoice?.invoiceTo?.email || '',
|
---|
57 | subject: `New Invoice for ${invoice?.month} - MVP Masters`,
|
---|
58 | message: `<p>Dear ${
|
---|
59 | invoice?.invoiceTo?.representative || ''
|
---|
60 | }, </p><p></p><p>We hope this email finds you well. Please find attached the invoice for ${invoice?.month}, detailing the work MVP Masters has completed during that period. We appreciate your prompt attention to this invoice. </p><p></p><p>Should you have any questions or require further clarification on any items, please don't hesitate to reply to this email.</p><p></p><p>Thank you for your continued partnership and trust in MVP Masters.</p><p><br></p><p>Warm regards,</p><p>Finance Team</p><p>MVP Masters</p>`,
|
---|
61 | }),
|
---|
62 | [invoice]
|
---|
63 | );
|
---|
64 |
|
---|
65 | const methods = useForm({
|
---|
66 | defaultValues,
|
---|
67 | });
|
---|
68 |
|
---|
69 | const {
|
---|
70 | reset,
|
---|
71 | handleSubmit,
|
---|
72 | watch,
|
---|
73 | setValue,
|
---|
74 | formState: { isSubmitting },
|
---|
75 | } = methods;
|
---|
76 |
|
---|
77 | const values = watch();
|
---|
78 |
|
---|
79 | const onSubmit = handleSubmit(async (data) => {
|
---|
80 | try {
|
---|
81 | if (!invoice) return;
|
---|
82 | // await new Promise((resolve) => setTimeout(resolve, 500));
|
---|
83 |
|
---|
84 | const pdfFilename = invoice.pdfRef?.split('/').pop() as string;
|
---|
85 | const invoicePDFRef = ref(storage, invoice.pdfRef);
|
---|
86 | const url = await getDownloadURL(invoicePDFRef);
|
---|
87 |
|
---|
88 | const mailData = {
|
---|
89 | from: '<MVP Masters - Finance Team> finance@mvpmasters.com',
|
---|
90 | to: invoice.invoiceTo.email,
|
---|
91 | message: {
|
---|
92 | subject: data.subject,
|
---|
93 | html: data.message,
|
---|
94 | attachments: [
|
---|
95 | {
|
---|
96 | filename: pdfFilename,
|
---|
97 | path: url,
|
---|
98 | },
|
---|
99 | ],
|
---|
100 | },
|
---|
101 | };
|
---|
102 |
|
---|
103 | const invoiceData = {
|
---|
104 | sent: increment(1),
|
---|
105 | status: 'pending',
|
---|
106 | };
|
---|
107 |
|
---|
108 | const mailId = createDocId(collections.mail);
|
---|
109 |
|
---|
110 | // write to DB
|
---|
111 | await firestoreBatch<any>([
|
---|
112 | {
|
---|
113 | docPath: `${collections.mail}/${mailId}`,
|
---|
114 | type: 'set',
|
---|
115 | data: mailData,
|
---|
116 | },
|
---|
117 | {
|
---|
118 | docPath: `${collections.invoice}/${invoice.id}`,
|
---|
119 | type: 'set',
|
---|
120 | data: invoiceData,
|
---|
121 | },
|
---|
122 | ]);
|
---|
123 |
|
---|
124 | mutate(invoiceMutationKey);
|
---|
125 |
|
---|
126 | reset();
|
---|
127 | onCloseCompose();
|
---|
128 | enqueueSnackbar('Invoice sent!');
|
---|
129 | console.info('DATA', data);
|
---|
130 | } catch (error) {
|
---|
131 | console.error(error);
|
---|
132 | }
|
---|
133 | });
|
---|
134 |
|
---|
135 | return (
|
---|
136 | <Portal>
|
---|
137 | {(fullScreen.value || !smUp) && <Backdrop open sx={{ zIndex: ZINDEX }} />}
|
---|
138 |
|
---|
139 | <Paper
|
---|
140 | sx={{
|
---|
141 | right: 0,
|
---|
142 | bottom: 0,
|
---|
143 | borderRadius: 2,
|
---|
144 | display: 'flex',
|
---|
145 | position: 'fixed',
|
---|
146 | zIndex: ZINDEX + 1,
|
---|
147 | m: `${POSITION}px`,
|
---|
148 | overflow: 'hidden',
|
---|
149 | flexDirection: 'column',
|
---|
150 | boxShadow: (theme) => theme.customShadows.dropdown,
|
---|
151 | ...(fullScreen.value && {
|
---|
152 | m: 0,
|
---|
153 | right: POSITION / 2,
|
---|
154 | bottom: POSITION / 2,
|
---|
155 | width: `calc(100% - ${POSITION}px)`,
|
---|
156 | height: `calc(100% - ${POSITION}px)`,
|
---|
157 | }),
|
---|
158 | }}
|
---|
159 | >
|
---|
160 | <Stack
|
---|
161 | direction="row"
|
---|
162 | alignItems="center"
|
---|
163 | sx={{
|
---|
164 | bgcolor: 'background.neutral',
|
---|
165 | p: (theme) => theme.spacing(1.5, 1, 1.5, 2),
|
---|
166 | }}
|
---|
167 | >
|
---|
168 | <Typography variant="h6" sx={{ flexGrow: 1 }}>
|
---|
169 | Inv. {invoice?.invoiceNumber} - {invoice?.invoiceTo?.name}
|
---|
170 | </Typography>
|
---|
171 |
|
---|
172 | <IconButton onClick={fullScreen.onToggle}>
|
---|
173 | <Iconify icon={fullScreen ? 'eva:collapse-fill' : 'eva:expand-fill'} />
|
---|
174 | </IconButton>
|
---|
175 |
|
---|
176 | <IconButton onClick={onCloseCompose}>
|
---|
177 | <Iconify icon="mingcute:close-line" />
|
---|
178 | </IconButton>
|
---|
179 | </Stack>
|
---|
180 |
|
---|
181 | <FormProvider methods={methods} onSubmit={onSubmit}>
|
---|
182 | <RHFTextField sx={{ px: 2, py: 2 }} variant="standard" name="to" placeholder="To" />
|
---|
183 | <RHFTextField
|
---|
184 | sx={{ px: 2, py: 1 }}
|
---|
185 | variant="standard"
|
---|
186 | name="subject"
|
---|
187 | placeholder="Subject"
|
---|
188 | />
|
---|
189 |
|
---|
190 | {/* <InputBase
|
---|
191 | placeholder="To"
|
---|
192 | // endAdornment={
|
---|
193 | // <Stack direction="row" spacing={0.5} sx={{ typography: 'subtitle2' }}>
|
---|
194 | // <Box
|
---|
195 | // sx={{
|
---|
196 | // cursor: 'pointer',
|
---|
197 | // '&:hover': { textDecoration: 'underline' },
|
---|
198 | // }}
|
---|
199 | // >
|
---|
200 | // Cc
|
---|
201 | // </Box>
|
---|
202 | // <Box
|
---|
203 | // sx={{
|
---|
204 | // cursor: 'pointer',
|
---|
205 | // '&:hover': { textDecoration: 'underline' },
|
---|
206 | // }}
|
---|
207 | // >
|
---|
208 | // Bcc
|
---|
209 | // </Box>
|
---|
210 | // </Stack>
|
---|
211 | // }
|
---|
212 | sx={{
|
---|
213 | px: 2,
|
---|
214 | height: 48,
|
---|
215 | borderBottom: (theme) => `solid 1px ${alpha(theme.palette.grey[500], 0.08)}`,
|
---|
216 | }}
|
---|
217 | /> */}
|
---|
218 |
|
---|
219 | {/* <InputBase
|
---|
220 | placeholder="Subject"
|
---|
221 | sx={{
|
---|
222 | px: 2,
|
---|
223 | height: 48,
|
---|
224 | borderBottom: (theme) => `solid 1px ${alpha(theme.palette.grey[500], 0.08)}`,
|
---|
225 | }}
|
---|
226 | /> */}
|
---|
227 |
|
---|
228 | <Stack spacing={2} flexGrow={1} sx={{ p: 2 }}>
|
---|
229 | <Editor
|
---|
230 | simple
|
---|
231 | id="compose-mail"
|
---|
232 | // value={message}
|
---|
233 | // onChange={handleChangeMessage}
|
---|
234 | value={values.message}
|
---|
235 | onChange={(value) => setValue('message', value)}
|
---|
236 | placeholder="Type a message"
|
---|
237 | sx={{
|
---|
238 | '& .ql-editor': {},
|
---|
239 | ...(fullScreen.value && {
|
---|
240 | height: 1,
|
---|
241 | '& .quill': {
|
---|
242 | height: 1,
|
---|
243 | },
|
---|
244 | '& .ql-editor': {
|
---|
245 | maxHeight: 'unset',
|
---|
246 | },
|
---|
247 | }),
|
---|
248 | }}
|
---|
249 | />
|
---|
250 |
|
---|
251 | <Stack direction="row" alignItems="center">
|
---|
252 | <Stack direction="row" alignItems="center" flexGrow={1}>
|
---|
253 | <IconButton>
|
---|
254 | <Iconify icon="solar:gallery-add-bold" />
|
---|
255 | </IconButton>
|
---|
256 |
|
---|
257 | <IconButton>
|
---|
258 | <Iconify icon="eva:attach-2-fill" />
|
---|
259 | </IconButton>
|
---|
260 | </Stack>
|
---|
261 |
|
---|
262 | <LoadingButton
|
---|
263 | type="submit"
|
---|
264 | variant="contained"
|
---|
265 | color="primary"
|
---|
266 | loading={isSubmitting}
|
---|
267 | endIcon={<Iconify icon="iconamoon:send-fill" />}
|
---|
268 | >
|
---|
269 | Send
|
---|
270 | </LoadingButton>
|
---|
271 | </Stack>
|
---|
272 | </Stack>
|
---|
273 | </FormProvider>
|
---|
274 | </Paper>
|
---|
275 | </Portal>
|
---|
276 | );
|
---|
277 | }
|
---|