Changeset 057453c for src/sections/invoice
- Timestamp:
- 02/26/25 10:05:32 (6 weeks ago)
- Branches:
- main
- Children:
- 299af01
- Parents:
- 5d6f37a
- Location:
- src/sections/invoice
- Files:
-
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sections/invoice/invoice-details.tsx
r5d6f37a r057453c 18 18 import { fCurrency } from 'src/utils/format-number'; 19 19 // types 20 import { Invoice } from ' mvpmasters-shared';20 import { Invoice } from 'src/schemas'; 21 21 // components 22 22 import Label from 'src/components/label'; … … 28 28 import { getQuantityType } from 'src/utils/get-invoice-quantity-type'; 29 29 import InvoiceToolbar from './invoice-toolbar'; 30 import { updateInvoice } from 'src/api/invoice'; 30 31 31 32 // ---------------------------------------------------------------------- … … 59 60 const handleChangeStatus = useCallback( 60 61 async (event: React.ChangeEvent<HTMLInputElement>) => { 61 await updateDocument(collections.invoice, invoice.id, { status: event.target.value }); 62 // await updateDocument(collections.invoice, invoice.id, { status: event.target.value }); 63 await updateInvoice(invoice.id, { 64 status: event.target.value as 'draft' | 'processing' | 'pending' | 'overdue' | 'paid', 65 }); 62 66 mutate([collections.invoice, invoice.id]); 63 67 }, … … 240 244 {invoice.invoiceTo.name} 241 245 <br /> 242 {!!invoice.invoiceTo.company Id&& (246 {!!invoice.invoiceTo.companyNumber && ( 243 247 <> 244 Company ID: {invoice.invoiceTo.company Id}248 Company ID: {invoice.invoiceTo.companyNumber} 245 249 <br /> 246 250 </> … … 257 261 Date Issued 258 262 </Typography> 259 {fDate(invoice.createDate .toDate())}263 {fDate(invoice.createDate)} 260 264 </Stack> 261 265 … … 264 268 Due Date 265 269 </Typography> 266 {fDate(invoice.dueDate .toDate())}270 {fDate(invoice.dueDate)} 267 271 </Stack> 268 272 </Box> -
src/sections/invoice/invoice-ee-pdf.tsx
r5d6f37a r057453c 5 5 import { fCurrency } from 'src/utils/format-number'; 6 6 // types 7 import { Invoice } from ' mvpmasters-shared';7 import { Invoice } from 'src/schemas'; 8 8 import { createFullAddress } from 'src/utils/create-full-address'; 9 9 import { getQuantityType } from 'src/utils/get-invoice-quantity-type'; … … 229 229 <View style={styles.col6}> 230 230 <Text style={[styles.subtitle1, styles.mb4]}>Date Issued</Text> 231 <Text style={styles.body2}>{fDate(createDate .toDate())}</Text>231 <Text style={styles.body2}>{fDate(createDate)}</Text> 232 232 </View> 233 233 <View style={styles.col6}> 234 234 <Text style={[styles.subtitle1, styles.mb4]}>Due date</Text> 235 <Text style={styles.body2}>{fDate(dueDate .toDate())}</Text>235 <Text style={styles.body2}>{fDate(dueDate)}</Text> 236 236 </View> 237 237 </View> -
src/sections/invoice/invoice-mk-pdf.tsx
r5d6f37a r057453c 5 5 import { fCurrency } from 'src/utils/format-number'; 6 6 // types 7 import { Invoice } from ' mvpmasters-shared';7 import { Invoice } from 'src/schemas'; 8 8 import { createFullAddress } from 'src/utils/create-full-address'; 9 9 import { getQuantityType } from 'src/utils/get-invoice-quantity-type'; … … 179 179 <View style={styles.col6}> 180 180 <Text style={[styles.subtitle1, styles.mb4]}>Date Issued</Text> 181 <Text style={styles.body2}>{fDate(createDate .toDate())}</Text>181 <Text style={styles.body2}>{fDate(createDate)}</Text> 182 182 </View> 183 183 <View style={styles.col6}> 184 184 <Text style={[styles.subtitle1, styles.mb4]}>Due date</Text> 185 <Text style={styles.body2}>{fDate(dueDate .toDate())}</Text>185 <Text style={styles.body2}>{fDate(dueDate)}</Text> 186 186 </View> 187 187 </View> -
src/sections/invoice/invoice-new-edit-address.tsx
r5d6f37a r057453c 7 7 import Typography from '@mui/material/Typography'; 8 8 // hooks 9 import { useGet Settings } from 'src/api/settings';9 import { useGetTenant } from 'src/api/tenant'; 10 10 import { useBoolean } from 'src/hooks/use-boolean'; 11 11 import { useResponsive } from 'src/hooks/use-responsive'; … … 32 32 const { invoiceFrom, invoiceTo } = values; 33 33 34 const { settings } = useGetSettings();34 const { settings: tenant } = useGetTenant(); 35 35 const { customers } = useGetCustomers(); 36 36 … … 100 100 </Stack> 101 101 102 { settings&& (102 {tenant && ( 103 103 <CompanyListDialog 104 104 title="Companies" … … 106 106 onClose={from.onFalse} 107 107 selected={(selectedId: string) => invoiceFrom?.id === selectedId} 108 onSelect={( company) => setValue('invoiceFrom', company)}109 list={[settings?.company, settings?.['company-ee']]}108 onSelect={(tenant) => setValue('invoiceFrom', tenant)} 109 tenant={tenant} 110 110 action={ 111 111 <Button -
src/sections/invoice/invoice-new-edit-details.tsx
r5d6f37a r057453c 14 14 import { fCurrency } from 'src/utils/format-number'; 15 15 // types 16 import { InvoiceItem } from ' mvpmasters-shared';16 import { InvoiceItem } from 'src/schemas'; 17 17 // components 18 18 import Iconify from 'src/components/iconify'; … … 75 75 const handleSelectService = useCallback( 76 76 (index: number, option: string) => { 77 setValue( 78 `items[${index}].price`, 79 invoiceServices.find((service) => service.id === option)?.price?.[ 80 values.quantityType?.toLowerCase() as 'sprint' | 'hour' | 'month' 81 ] 82 ); 83 setValue( 84 `items[${index}].total`, 85 values.items.map((item: InvoiceItem) => item.quantity * item.price)[index] 86 ); 77 const service = invoiceServices.find((service) => service.id === option); 78 if (!service) return; 79 80 const quantityType = values.quantityType?.toLowerCase(); 81 let price = 0; 82 83 switch (quantityType) { 84 case 'sprint': 85 price = service.sprint; 86 break; 87 case 'hour': 88 price = service.hour; 89 break; 90 case 'month': 91 price = service.month; 92 break; 93 default: 94 price = 0; 95 } 96 97 setValue(`items[${index}].price`, price); 98 setValue(`items[${index}].total`, values.items[index].quantity * price); 87 99 }, 88 100 [setValue, values.items, values.quantityType, invoiceServices] -
src/sections/invoice/invoice-new-edit-form.tsx
r5d6f37a r057453c 11 11 import { useRouter } from 'src/routes/hooks'; 12 12 // types 13 import { Invoice } from 'mvpmasters-shared';13 import { CreateInvoice, Invoice } from 'src/schemas'; 14 14 // hooks 15 15 import { useBoolean } from 'src/hooks/use-boolean'; … … 20 20 import uploadToFirebaseStorage from 'src/utils/upload-to-firebase-storage'; 21 21 import { pdf } from '@react-pdf/renderer'; 22 import { useGetSettings } from 'src/api/settings'; 23 import { 24 collections, 25 documents, 26 firestoreBatch, 27 generateId, 28 updateDocument, 29 } from 'src/lib/firestore'; 22 import { useGetTenant } from 'src/api/tenant'; 23 import { collections, generateId, updateDocument } from 'src/lib/firestore'; 30 24 import { useGetServices } from 'src/api/service'; 31 25 import { mutate } from 'swr'; 32 import { Timestamp } from 'firebase/firestore';33 26 import InvoiceNewEditStatusDate from './invoice-new-edit-status-date'; 34 27 import InvoiceNewEditAddress from './invoice-new-edit-address'; 35 28 import InvoiceNewEditDetails from './invoice-new-edit-details'; 36 29 import InvoicePDF from './invoice-pdf'; 30 import { createInvoice, updateInvoice } from 'src/api/invoice'; 37 31 38 32 // ---------------------------------------------------------------------- … … 58 52 ]; 59 53 54 interface InvoiceItem { 55 service: string | null; 56 title: string; 57 price: number; 58 total: number; 59 quantity: number; 60 description: string; 61 } 62 60 63 export default function InvoiceNewEditForm({ isCopy, currentInvoice }: Props) { 61 64 const router = useRouter(); … … 69 72 description: Yup.string().required('Description is required'), 70 73 service: Yup.string().nullable(), 71 quantity: Yup.number().required('Quantity is required').min(0.5, 'Quantity must be at least 1'), 74 quantity: Yup.number() 75 .required('Quantity is required') 76 .min(0.5, 'Quantity must be at least 0.5'), 72 77 price: Yup.number().required('Price is required').min(0, 'Price must be at least 0'), 73 78 total: Yup.number().required('Total is required').min(0, 'Total must be at least 0'), … … 91 96 .required('Quantity type is required'), 92 97 month: Yup.string().oneOf(monthNames).required('Month is required'), 93 status: Yup.string(). required(),98 status: Yup.string().oneOf(['draft', 'processing', 'pending', 'overdue', 'paid']).required(), 94 99 totalAmount: Yup.number().required(), 95 100 // not required … … 103 108 104 109 const { services: invoiceServices } = useGetServices(); 105 const { settings, settingsEmpty, settingsLoading } = useGetSettings(); 110 console.log('invoiceServices', invoiceServices); 111 const { settings: tenant, settingsEmpty, settingsLoading } = useGetTenant(); 106 112 107 113 const defaultValues = useMemo( … … 110 116 !isCopy && currentInvoice?.invoiceNumber 111 117 ? currentInvoice?.invoiceNumber 112 : incrementInvoiceNumber(settings?.invoice?.lastInvoiceNumber), 113 createDate: currentInvoice?.createDate?.toDate() || new Date(), 114 dueDate: currentInvoice?.dueDate?.toDate() || null, 118 : incrementInvoiceNumber(tenant?.lastInvoiceNumber), 119 createDate: currentInvoice?.createDate ? new Date(currentInvoice.createDate) : new Date(), 120 dueDate: currentInvoice?.dueDate 121 ? new Date(currentInvoice.dueDate) 122 : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), 115 123 invoiceFrom: currentInvoice?.invoiceFrom || null, 116 124 invoiceTo: currentInvoice?.invoiceTo || null, … … 138 146 totalAmount: currentInvoice?.totalAmount || 0, 139 147 }), 140 [currentInvoice, isCopy, settings]148 [currentInvoice, isCopy, tenant] 141 149 ); 142 150 … … 164 172 165 173 try { 166 // generate collection id167 174 const id = generateId(collections.invoice); 175 176 // Ensure dates are valid Date objects 177 const createDate = 178 data.createDate instanceof Date ? data.createDate : new Date(data.createDate); 179 const dueDate = data.dueDate instanceof Date ? data.dueDate : new Date(data.dueDate); 180 181 const currentTime = new Date(); 182 createDate.setHours( 183 currentTime.getHours(), 184 currentTime.getMinutes(), 185 currentTime.getSeconds() 186 ); 168 187 169 188 // attach serivce details … … 173 192 })); 174 193 175 const currentTime = new Date();176 const createDateWithCurrentTime = new Date(data.createDate); // This creates a date object using the date from data.createDate177 // Set the time of createDateWithCurrentTime to the current hour, minutes, and seconds178 createDateWithCurrentTime.setHours(179 currentTime.getHours(),180 currentTime.getMinutes(),181 currentTime.getSeconds()182 );183 184 194 // transform data 185 const writeData = {195 const writeData: CreateInvoice = { 186 196 ...data, 187 invoiceNumber: incrementInvoiceNumber( settings?.invoice.lastInvoiceNumber),197 invoiceNumber: incrementInvoiceNumber(tenant?.lastInvoiceNumber), 188 198 status: 'draft', 189 createDate: Timestamp.fromDate(createDateWithCurrentTime), 190 dueDate: Timestamp.fromDate(new Date(data.dueDate)), 191 items, 199 createDate, 200 dueDate, 201 items: items.filter((item) => item.service !== null) as CreateInvoice['items'], 202 subTotal: data.totalAmount - (data.taxes || 0) - (data.discount || 0), 203 month: data.month as Invoice['month'], 204 invoiceFrom: data.invoiceFrom!, 205 invoiceTo: data.invoiceTo!, 192 206 }; 193 207 … … 199 213 200 214 // write to DB 201 await firestoreBatch([ 202 { 203 docPath: `${collections.invoice}/${id}`, 204 type: 'set', 205 data: { 206 ...writeData, 207 pdfRef: storagePath, 208 }, 209 }, 210 { 211 docPath: `${collections.settings}/${documents.settingsInvoice}`, 212 type: 'set', 213 data: { lastInvoiceNumber: writeData.invoiceNumber }, 214 }, 215 ]); 215 // await firestoreBatch([ 216 // { 217 // docPath: `${collections.invoice}/${id}`, 218 // type: 'set', 219 // data: { 220 // ...writeData, 221 // pdfRef: storagePath, 222 // }, 223 // }, 224 // { 225 // docPath: `${collections.settings}/${documents.settingsInvoice}`, 226 // type: 'set', 227 // data: { lastInvoiceNumber: writeData.invoiceNumber }, 228 // }, 229 // ]); 230 231 await createInvoice({ ...writeData, pdfRef: storagePath }); 216 232 217 233 loadingSave.onFalse(); … … 229 245 try { 230 246 if (currentInvoice) { 247 // Ensure dates are valid Date objects 248 const createDate = 249 data.createDate instanceof Date ? data.createDate : new Date(data.createDate); 250 const dueDate = data.dueDate instanceof Date ? data.dueDate : new Date(data.dueDate); 251 231 252 // attach serivce details 232 253 const items = data.items.map((item) => ({ … … 238 259 const writeData = { 239 260 ...data, 240 createDate : Timestamp.fromDate(new Date(data.createDate)),241 dueDate : Timestamp.fromDate(new Date(data.dueDate)),261 createDate, 262 dueDate, 242 263 items, 264 invoiceFrom: data.invoiceFrom!, 265 invoiceTo: data.invoiceTo!, 243 266 }; 244 267 … … 250 273 251 274 // update DB 252 await updateDocument(collections.invoice, currentInvoice.id, { 275 // await updateDocument(collections.invoice, currentInvoice.id, { 276 // ...writeData, 277 // pdfRef: storagePath, 278 // }); 279 280 await updateInvoice(currentInvoice.id, { 253 281 ...writeData, 254 282 pdfRef: storagePath, 283 status: data.status as 'draft' | 'processing' | 'pending' | 'overdue' | 'paid', 284 month: data.month as 285 | 'January' 286 | 'February' 287 | 'March' 288 | 'April' 289 | 'May' 290 | 'June' 291 | 'July' 292 | 'August' 293 | 'September' 294 | 'October' 295 | 'November' 296 | 'December', 297 items: items.filter((item) => item.service !== null) as { 298 service: { id: string; month: number; name: string; sprint: number; hour: number }; 299 title: string; 300 price: number; 301 total: number; 302 quantity: number; 303 description: string; 304 }[], 255 305 }); 256 306 257 307 // mutate current data 258 mutate([collections.invoice, currentInvoice.id]);308 // mutate([collections.invoice, currentInvoice.id]); 259 309 } else { 260 310 // generate collection id 261 311 const id = generateId(collections.invoice); 312 313 // Ensure dates are valid Date objects 314 const createDate = 315 data.createDate instanceof Date ? data.createDate : new Date(data.createDate); 316 const dueDate = data.dueDate instanceof Date ? data.dueDate : new Date(data.dueDate); 262 317 263 318 // attach serivce details … … 270 325 const writeData = { 271 326 ...data, 272 createDate : Timestamp.fromDate(new Date(data.createDate)),273 dueDate : Timestamp.fromDate(new Date(data.dueDate)),327 createDate, 328 dueDate, 274 329 items, 330 invoiceFrom: data.invoiceFrom!, 331 invoiceTo: data.invoiceTo!, 275 332 }; 276 333 … … 282 339 283 340 // write to DB 284 await firestoreBatch([ 285 { 286 docPath: `${collections.invoice}/${id}`, 287 type: 'set', 288 data: { 289 ...writeData, 290 pdfRef: storagePath, 291 }, 292 }, 293 { 294 docPath: `${collections.settings}/${documents.settingsInvoice}`, 295 type: 'set', 296 data: { lastInvoiceNumber: writeData.invoiceNumber }, 297 }, 298 ]); 341 // await firestoreBatch([ 342 // { 343 // docPath: `${collections.invoice}/${id}`, 344 // type: 'set', 345 // data: { 346 // ...writeData, 347 // pdfRef: storagePath, 348 // }, 349 // }, 350 // { 351 // docPath: `${collections.settings}/${documents.settingsInvoice}`, 352 // type: 'set', 353 // data: { lastInvoiceNumber: writeData.invoiceNumber }, 354 // }, 355 // ]); 356 await createInvoice({ ...writeData, pdfRef: storagePath }); 299 357 300 358 reset(); … … 314 372 <FormProvider methods={methods}> 315 373 <Card> 316 { settings?.company&& <InvoiceNewEditAddress />}374 {!!tenant && <InvoiceNewEditAddress />} 317 375 318 376 <InvoiceNewEditStatusDate isCopy={isCopy} /> -
src/sections/invoice/invoice-new-edit-status-date.tsx
r5d6f37a r057453c 7 7 import { RHFSelect, RHFTextField } from 'src/components/hook-form'; 8 8 // api 9 import { useGetSettings} from 'src/api/settings';9 // import { useGetTenant } from 'src/api/settings'; 10 10 // utils 11 11 import { incrementInvoiceNumber } from 'src/utils/increment-invoice-number'; … … 33 33 const values = watch(); 34 34 35 const { settings } = useGetSettings();35 // const { settings } = useGetTenant(); 36 36 37 37 return ( -
src/sections/invoice/invoice-pdf.tsx
r5d6f37a r057453c 1 import { Invoice } from ' mvpmasters-shared';1 import { Invoice } from 'src/schemas'; 2 2 import InvoiceEEPDF from './invoice-ee-pdf'; 3 3 import InvoiceMKPDF from './invoice-mk-pdf'; -
src/sections/invoice/invoice-table-filters-result.tsx
r5d6f37a r057453c 6 6 import Stack, { StackProps } from '@mui/material/Stack'; 7 7 // types 8 import { InvoiceTableFilters, InvoiceTableFilterValue } from ' mvpmasters-shared';8 import { InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas'; 9 9 // components 10 10 import Iconify from 'src/components/iconify'; -
src/sections/invoice/invoice-table-row.tsx
r5d6f37a r057453c 16 16 import { fNumber } from 'src/utils/format-number'; 17 17 // types 18 import { Invoice } from ' mvpmasters-shared';18 import { Invoice } from 'src/schemas'; 19 19 // components 20 20 import Label from 'src/components/label'; … … 60 60 } = row; 61 61 62 console.log(createDate); 63 62 64 const confirmSend = useBoolean(); 63 65 const confirmDelete = useBoolean(); … … 105 107 <TableCell> 106 108 <ListItemText 107 primary={format( createDate.toDate(), 'dd MMM yyyy')}108 secondary={format( createDate.toDate(), 'p')}109 primary={format(new Date(createDate), 'dd MMM yyyy')} 110 secondary={format(new Date(createDate), 'p')} 109 111 primaryTypographyProps={{ typography: 'body2', noWrap: true }} 110 112 secondaryTypographyProps={{ … … 118 120 <TableCell> 119 121 <ListItemText 120 primary={format( dueDate.toDate(), 'dd MMM yyyy')}121 secondary={format( dueDate.toDate(), 'p')}122 primary={format(new Date(dueDate), 'dd MMM yyyy')} 123 secondary={format(new Date(dueDate), 'p')} 122 124 primaryTypographyProps={{ typography: 'body2', noWrap: true }} 123 125 secondaryTypographyProps={{ -
src/sections/invoice/invoice-table-toolbar.tsx
r5d6f37a r057453c 7 7 import InputAdornment from '@mui/material/InputAdornment'; 8 8 // types 9 import { InvoiceTableFilters, InvoiceTableFilterValue } from ' mvpmasters-shared';9 import { InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas'; 10 10 // components 11 11 import Iconify from 'src/components/iconify'; -
src/sections/invoice/invoice-toolbar.tsx
r5d6f37a r057453c 18 18 import { useBoolean } from 'src/hooks/use-boolean'; 19 19 // types 20 import { Invoice } from ' mvpmasters-shared';20 import { Invoice } from 'src/schemas'; 21 21 // components 22 22 import Iconify from 'src/components/iconify'; -
src/sections/invoice/mail-compose.tsx
r5d6f37a r057453c 13 13 import Iconify from 'src/components/iconify'; 14 14 import Editor from 'src/components/editor'; 15 import { Invoice } from ' mvpmasters-shared';15 import { Invoice } from 'src/schemas'; 16 16 import FormProvider from 'src/components/hook-form/form-provider'; 17 17 import { RHFTextField } from 'src/components/hook-form'; -
src/sections/invoice/view/invoice-edit-view.tsx
r5d6f37a r057453c 23 23 24 24 const { currentInvoice } = useGetInvoice({ id }); 25 console.log('currentInvoice', currentInvoice); 25 26 26 27 return ( -
src/sections/invoice/view/invoice-list-view.tsx
r5d6f37a r057453c 36 36 } from 'src/components/table'; 37 37 // types 38 import { 39 Invoice, 40 InvoiceStatus, 41 InvoiceTableFilters, 42 InvoiceTableFilterValue, 43 } from 'mvpmasters-shared'; 38 import { Invoice, InvoiceStatus, InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas'; 44 39 // 45 40 import deleteFromFirebaseStorage from 'src/utils/delete-from-firebase-storage'; 46 41 // fetch 47 import { use GetInvoices } from 'src/api/invoice';42 import { useDeleteInvoice, useGetInvoices } from 'src/api/invoice'; 48 43 import { collections, removeDocument } from 'src/lib/firestore'; 49 44 import { mutate } from 'swr'; … … 163 158 const [filters, setFilters] = useState(defaultFilters); 164 159 165 const { invoices: tableData } = useGetInvoices({ 166 where: [['createDate', '>=', filters.startDate]], 167 orderBy: 'createDate', 168 direction: 'desc', 169 }); 160 const { invoices: tableData } = useGetInvoices({ startDate: filters.startDate?.toISOString() }); 170 161 171 162 const invoiceMutationKey = useMemo( … … 269 260 ); 270 261 262 const { deleteInvoiceMutation } = useDeleteInvoice(); 263 271 264 const handleDeleteRow = useCallback( 272 265 async (invoice: Invoice) => { … … 275 268 orderBy: 'createDate', 276 269 direction: 'desc', 277 }); // Get the same params as used in useGetInvoices 278 279 // Optimistically update the cache before the deletion 280 // mutate( 281 // [collections.invoice, serializedParams], 282 // (invoices: Invoice[] = []) => invoices.filter((row) => row.id !== invoice.id), 283 // false 284 // ); 285 286 await removeDocument(collections.invoice, invoice.id); 270 }); 271 272 await deleteInvoiceMutation(invoice.id); 287 273 await deleteFromFirebaseStorage( 288 274 `invoices/${invoice.invoiceTo.name}/${invoice.id}-${invoice.invoiceNumber}.pdf` 289 275 ); 290 276 291 // Optionally, rollback optimistic update or refetch data292 277 mutate(invoiceMutationKey); 293 278 }, … … 672 657 inputData = inputData.filter( 673 658 (invoice) => 674 fTimestamp(invoice.createDate. toMillis()) >= fTimestamp(startDate) &&675 fTimestamp(invoice.createDate. toMillis()) <= fTimestamp(endDate)659 fTimestamp(invoice.createDate.getTime()) >= fTimestamp(startDate.getTime()) && 660 fTimestamp(invoice.createDate.getTime()) <= fTimestamp(endDate.getTime()) 676 661 ); 677 662 }
Note:
See TracChangeset
for help on using the changeset viewer.