initial commit
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Button from '@mui/material/Button'
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Switch from '@mui/material/Switch'
|
||||
|
||||
// Type Imports
|
||||
import type { Locale } from '@configs/i18n'
|
||||
|
||||
// Component Imports
|
||||
import SendInvoiceDrawer from '@views/apps/invoice/shared/SendInvoiceDrawer'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
// Util Imports
|
||||
import { getLocalizedUrl } from '@/utils/i18n'
|
||||
|
||||
const AddActions = () => {
|
||||
// States
|
||||
const [sendDrawerOpen, setSendDrawerOpen] = useState(false)
|
||||
|
||||
// Hooks
|
||||
const { lang: locale } = useParams()
|
||||
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col gap-4'>
|
||||
<Button
|
||||
fullWidth
|
||||
variant='contained'
|
||||
className='capitalize'
|
||||
startIcon={<i className='tabler-send' />}
|
||||
onClick={() => setSendDrawerOpen(true)}
|
||||
>
|
||||
Send Invoice
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
component={Link}
|
||||
color='secondary'
|
||||
variant='tonal'
|
||||
className='capitalize'
|
||||
href={getLocalizedUrl('/apps/invoice/preview/4987', locale as Locale)}
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
<Button fullWidth color='secondary' variant='tonal' className='capitalize'>
|
||||
Save
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<SendInvoiceDrawer open={sendDrawerOpen} handleClose={() => setSendDrawerOpen(false)} />
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<CustomTextField select fullWidth defaultValue='Internet Banking' label='Accept payments via'>
|
||||
<MenuItem value='Internet Banking'>Internet Banking</MenuItem>
|
||||
<MenuItem value='Debit Card'>Debit Card</MenuItem>
|
||||
<MenuItem value='Credit Card'>Credit Card</MenuItem>
|
||||
<MenuItem value='Paypal'>Paypal</MenuItem>
|
||||
<MenuItem value='UPI Transfer'>UPI Transfer</MenuItem>
|
||||
</CustomTextField>
|
||||
<div className='flex items-center justify-between mbs-3'>
|
||||
<InputLabel htmlFor='invoice-edit-payment-terms' className='cursor-pointer'>
|
||||
Payment Terms
|
||||
</InputLabel>
|
||||
<Switch defaultChecked id='invoice-edit-payment-terms' />
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<InputLabel htmlFor='invoice-edit-client-notes' className='cursor-pointer'>
|
||||
Client Notes
|
||||
</InputLabel>
|
||||
<Switch id='invoice-edit-client-notes' />
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<InputLabel htmlFor='invoice-edit-payment-stub' className='cursor-pointer'>
|
||||
Payment Stub
|
||||
</InputLabel>
|
||||
<Switch id='invoice-edit-payment-stub' />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddActions
|
||||
@@ -0,0 +1,372 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { SyntheticEvent } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import InputAdornment from '@mui/material/InputAdornment'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Button from '@mui/material/Button'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { InvoiceType } from '@/types/apps/invoiceTypes'
|
||||
import type { FormDataType } from './AddCustomerDrawer'
|
||||
|
||||
// Component Imports
|
||||
import AddCustomerDrawer, { initialFormData } from './AddCustomerDrawer'
|
||||
import Logo from '@components/layout/shared/Logo'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
// Styled Component Imports
|
||||
import AppReactDatepicker from '@/libs/styles/AppReactDatepicker'
|
||||
|
||||
const AddAction = ({ invoiceData }: { invoiceData?: InvoiceType[] }) => {
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
const [count, setCount] = useState(1)
|
||||
const [selectData, setSelectData] = useState<InvoiceType | null>(null)
|
||||
const [issuedDate, setIssuedDate] = useState<Date | null | undefined>(null)
|
||||
const [dueDate, setDueDate] = useState<Date | null | undefined>(null)
|
||||
const [formData, setFormData] = useState<FormDataType>(initialFormData)
|
||||
|
||||
// Hooks
|
||||
const isBelowMdScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'))
|
||||
const isBelowSmScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'))
|
||||
|
||||
const onFormSubmit = (data: FormDataType) => {
|
||||
setFormData(data)
|
||||
}
|
||||
|
||||
const deleteForm = (e: SyntheticEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
// @ts-ignore
|
||||
e.target.closest('.repeater-item').remove()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className='sm:!p-12'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='p-6 bg-actionHover rounded'>
|
||||
<div className='flex justify-between gap-4 flex-col sm:flex-row'>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<Logo />
|
||||
</div>
|
||||
<div>
|
||||
<Typography color='text.primary'>Office 149, 450 South Brand Brooklyn</Typography>
|
||||
<Typography color='text.primary'>San Diego County, CA 91905, USA</Typography>
|
||||
<Typography color='text.primary'>+1 (123) 456 7891, +44 (876) 543 2198</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography variant='h5' className='min-is-[95px]'>
|
||||
Invoice
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
value={invoiceData?.[0].id}
|
||||
slotProps={{
|
||||
input: {
|
||||
disabled: true,
|
||||
startAdornment: <InputAdornment position='start'>#</InputAdornment>
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Typography className='min-is-[95px] mie-4' color='text.primary'>
|
||||
Date Issued:
|
||||
</Typography>
|
||||
<AppReactDatepicker
|
||||
boxProps={{ className: 'is-full' }}
|
||||
selected={issuedDate}
|
||||
placeholderText='YYYY-MM-DD'
|
||||
dateFormat={'yyyy-MM-dd'}
|
||||
onChange={(date: Date | null) => setIssuedDate(date)}
|
||||
customInput={<CustomTextField fullWidth />}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Typography className='min-is-[95px] mie-4' color='text.primary'>
|
||||
Date Due:
|
||||
</Typography>
|
||||
<AppReactDatepicker
|
||||
boxProps={{ className: 'is-full' }}
|
||||
selected={dueDate}
|
||||
placeholderText='YYYY-MM-DD'
|
||||
dateFormat={'yyyy-MM-dd'}
|
||||
onChange={(date: Date | null) => setDueDate(date)}
|
||||
customInput={<CustomTextField fullWidth />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='flex justify-between flex-col gap-4 flex-wrap sm:flex-row'>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Invoice To:
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
select
|
||||
className={classnames('min-is-[220px]', { 'is-1/2': isBelowSmScreen })}
|
||||
value={selectData?.id || ''}
|
||||
onChange={e => {
|
||||
setFormData({} as FormDataType)
|
||||
setSelectData(invoiceData?.slice(0, 5).filter(item => item.id === e.target.value)[0] || null)
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
className='flex items-center gap-2 !text-success !bg-transparent hover:text-success hover:!bg-[var(--mui-palette-success-lightOpacity)]'
|
||||
value=''
|
||||
onClick={() => {
|
||||
setSelectData(null)
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<i className='tabler-plus text-base' />
|
||||
Add New Customer
|
||||
</MenuItem>
|
||||
{invoiceData?.slice(0, 5).map((invoice: InvoiceType, index) => (
|
||||
<MenuItem key={index} value={invoice.id}>
|
||||
{invoice.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</CustomTextField>
|
||||
{selectData?.id ? (
|
||||
<div>
|
||||
<Typography>{selectData?.name}</Typography>
|
||||
<Typography>{selectData?.company}</Typography>
|
||||
<Typography>{selectData?.address}</Typography>
|
||||
<Typography>{selectData?.contact}</Typography>
|
||||
<Typography>{selectData?.companyEmail}</Typography>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Typography>{formData?.name}</Typography>
|
||||
<Typography>{formData?.company}</Typography>
|
||||
<Typography>{formData?.address}</Typography>
|
||||
<Typography>{formData?.contactNumber}</Typography>
|
||||
<Typography>{formData?.email}</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Bill To:
|
||||
</Typography>
|
||||
<div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Total Due:</Typography>
|
||||
<Typography>$12,110.55</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Bank name:</Typography>
|
||||
<Typography>American Bank</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Country:</Typography>
|
||||
<Typography>United States</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>IBAN:</Typography>
|
||||
<Typography>ETD95476213874685</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>SWIFT code:</Typography>
|
||||
<Typography>BR91905</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
{Array.from(Array(count).keys()).map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={classnames('repeater-item flex relative mbe-4 border rounded', {
|
||||
'mbs-8': !isBelowMdScreen,
|
||||
'!mbs-14': index !== 0 && !isBelowMdScreen,
|
||||
'gap-5': isBelowMdScreen
|
||||
})}
|
||||
>
|
||||
<Grid container spacing={5} className='m-0 p-5'>
|
||||
<Grid size={{ xs: 12, md: 5, lg: 6 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8' color='text.primary'>
|
||||
Item
|
||||
</Typography>
|
||||
<CustomTextField select fullWidth defaultValue='App Design' className='mbe-5'>
|
||||
<MenuItem value='App Design'>App Design</MenuItem>
|
||||
<MenuItem value='App Customization'>App Customization</MenuItem>
|
||||
<MenuItem value='ABC Template'>ABC Template</MenuItem>
|
||||
<MenuItem value='App Development'>App Development</MenuItem>
|
||||
</CustomTextField>
|
||||
<CustomTextField rows={2} fullWidth multiline defaultValue='Customization & Bug Fixes' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 3, lg: 2 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8'>Cost</Typography>
|
||||
<CustomTextField
|
||||
{...(isBelowMdScreen && { fullWidth: true })}
|
||||
type='number'
|
||||
placeholder='24'
|
||||
defaultValue='24'
|
||||
className='mbe-5'
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: { min: 0 }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className='flex flex-col'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
Discount:
|
||||
</Typography>
|
||||
<div className='flex gap-2'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
0%
|
||||
</Typography>
|
||||
<Tooltip title='Tax 1' placement='top'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
0%
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title='Tax 2' placement='top'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
0%
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 2 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8'>Hours</Typography>
|
||||
<CustomTextField
|
||||
{...(isBelowMdScreen && { fullWidth: true })}
|
||||
type='number'
|
||||
placeholder='1'
|
||||
defaultValue='1'
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: { min: 0 }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 2 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8'>Price</Typography>
|
||||
<Typography>$24.00</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<div className='flex flex-col justify-start border-is'>
|
||||
<IconButton size='small' onClick={deleteForm}>
|
||||
<i className='tabler-x text-2xl text-actionActive' />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Button
|
||||
size='small'
|
||||
variant='contained'
|
||||
onClick={() => setCount(count + 1)}
|
||||
startIcon={<i className='tabler-plus' />}
|
||||
>
|
||||
Add Item
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='flex justify-between flex-col gap-4 sm:flex-row'>
|
||||
<div className='flex flex-col gap-4 order-2 sm:order-[unset]'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Salesperson:
|
||||
</Typography>
|
||||
<CustomTextField defaultValue='Tommy Shelby' />
|
||||
</div>
|
||||
<CustomTextField placeholder='Thanks for your business' />
|
||||
</div>
|
||||
<div className='min-is-[200px]'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Subtotal:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$1800
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Discount:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$28
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Tax:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
21%
|
||||
</Typography>
|
||||
</div>
|
||||
<Divider className='mlb-2' />
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Total:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$1690
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<InputLabel htmlFor='invoice-note' className='inline-flex mbe-1 text-textPrimary'>
|
||||
Note:
|
||||
</InputLabel>
|
||||
<CustomTextField
|
||||
id='invoice-note'
|
||||
rows={2}
|
||||
fullWidth
|
||||
multiline
|
||||
className='border rounded'
|
||||
defaultValue='It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance
|
||||
projects. Thank You!'
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AddCustomerDrawer open={open} setOpen={setOpen} onFormSubmit={onFormSubmit} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddAction
|
||||
@@ -0,0 +1,144 @@
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { FormEvent } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Button from '@mui/material/Button'
|
||||
import Divider from '@mui/material/Divider'
|
||||
|
||||
// Component Imports
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
onFormSubmit: (formData: FormDataType) => void
|
||||
}
|
||||
|
||||
export type FormDataType = {
|
||||
name: string
|
||||
company: string
|
||||
email: string
|
||||
address: string
|
||||
country: string
|
||||
contactNumber: string
|
||||
}
|
||||
|
||||
// Vars
|
||||
export const initialFormData: FormDataType = {
|
||||
name: '',
|
||||
company: '',
|
||||
email: '',
|
||||
address: '',
|
||||
country: 'USA',
|
||||
contactNumber: ''
|
||||
}
|
||||
|
||||
const countries = ['USA', 'UK', 'Russia', 'Australia', 'Canada']
|
||||
|
||||
const AddCustomerDrawer = ({ open, setOpen, onFormSubmit }: Props) => {
|
||||
// States
|
||||
const [data, setData] = useState<FormDataType>(initialFormData)
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setOpen(false)
|
||||
onFormSubmit(data)
|
||||
handleReset()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
setOpen(false)
|
||||
setData(initialFormData)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={open}
|
||||
anchor='right'
|
||||
variant='temporary'
|
||||
onClose={handleReset}
|
||||
ModalProps={{ keepMounted: true }}
|
||||
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
|
||||
>
|
||||
<div className='flex items-center justify-between plb-5 pli-6'>
|
||||
<Typography variant='h5'>Add New Customer</Typography>
|
||||
<IconButton size='small' onClick={handleReset}>
|
||||
<i className='tabler-x text-2xl text-textPrimary' />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className='p-6'>
|
||||
<form onSubmit={e => handleSubmit(e)} className='flex flex-col gap-5'>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
id='name'
|
||||
label='Name'
|
||||
value={data.name}
|
||||
onChange={e => setData({ ...data, name: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
id='company'
|
||||
label='Company'
|
||||
value={data.company}
|
||||
onChange={e => setData({ ...data, company: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
id='email'
|
||||
label='Email'
|
||||
value={data.email}
|
||||
onChange={e => setData({ ...data, email: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
rows={6}
|
||||
multiline
|
||||
fullWidth
|
||||
id='address'
|
||||
label='Address'
|
||||
value={data.address}
|
||||
onChange={e => setData({ ...data, address: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
select
|
||||
id='country'
|
||||
label='Country'
|
||||
name='country'
|
||||
variant='outlined'
|
||||
value={data?.country?.toLowerCase().replace(/\s+/g, '-') || ''}
|
||||
onChange={e => setData({ ...data, country: e.target.value })}
|
||||
>
|
||||
{countries.map((item, index) => (
|
||||
<MenuItem key={index} value={item.toLowerCase().replace(/\s+/g, '-')}>
|
||||
{item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</CustomTextField>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
id='contact'
|
||||
type='number'
|
||||
label='Contact Number'
|
||||
value={data.contactNumber}
|
||||
onChange={e => setData({ ...data, contactNumber: e.target.value })}
|
||||
/>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button variant='contained' type='submit'>
|
||||
Add
|
||||
</Button>
|
||||
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddCustomerDrawer
|
||||
@@ -0,0 +1,114 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Button from '@mui/material/Button'
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Switch from '@mui/material/Switch'
|
||||
|
||||
// Type Imports
|
||||
import type { Locale } from '@configs/i18n'
|
||||
|
||||
// Component Imports
|
||||
import AddPaymentDrawer from '@views/apps/invoice/shared/AddPaymentDrawer'
|
||||
import SendInvoiceDrawer from '@views/apps/invoice/shared/SendInvoiceDrawer'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
// Util Imports
|
||||
import { getLocalizedUrl } from '@/utils/i18n'
|
||||
|
||||
const EditActions = ({ id }: { id: string }) => {
|
||||
// States
|
||||
const [paymentDrawerOpen, setPaymentDrawerOpen] = useState(false)
|
||||
const [sendDrawerOpen, setSendDrawerOpen] = useState(false)
|
||||
|
||||
// Hooks
|
||||
const { lang: locale } = useParams()
|
||||
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col gap-4'>
|
||||
<Button
|
||||
fullWidth
|
||||
variant='contained'
|
||||
className='capitalize'
|
||||
startIcon={<i className='tabler-send' />}
|
||||
onClick={() => setSendDrawerOpen(true)}
|
||||
>
|
||||
Send Invoice
|
||||
</Button>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button
|
||||
fullWidth
|
||||
component={Link}
|
||||
color='secondary'
|
||||
variant='tonal'
|
||||
className='capitalize'
|
||||
href={getLocalizedUrl(`/apps/invoice/preview/${id}`, locale as Locale)}
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
<Button fullWidth color='secondary' variant='tonal' className='capitalize'>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
fullWidth
|
||||
color='success'
|
||||
variant='contained'
|
||||
className='capitalize'
|
||||
onClick={() => setPaymentDrawerOpen(true)}
|
||||
startIcon={<i className='tabler-currency-dollar' />}
|
||||
>
|
||||
Add Payment
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AddPaymentDrawer open={paymentDrawerOpen} handleClose={() => setPaymentDrawerOpen(false)} />
|
||||
<SendInvoiceDrawer open={sendDrawerOpen} handleClose={() => setSendDrawerOpen(false)} />
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<CustomTextField select fullWidth defaultValue='Internet Banking' label='Accept payments via'>
|
||||
<MenuItem value='Internet Banking'>Internet Banking</MenuItem>
|
||||
<MenuItem value='Debit Card'>Debit Card</MenuItem>
|
||||
<MenuItem value='Credit Card'>Credit Card</MenuItem>
|
||||
<MenuItem value='Paypal'>Paypal</MenuItem>
|
||||
<MenuItem value='UPI Transfer'>UPI Transfer</MenuItem>
|
||||
</CustomTextField>
|
||||
<div className='flex items-center justify-between gap-6 mbs-3'>
|
||||
<InputLabel htmlFor='invoice-edit-payment-terms' className='cursor-pointer'>
|
||||
Payment Terms
|
||||
</InputLabel>
|
||||
<Switch defaultChecked id='invoice-edit-payment-terms' />
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-6'>
|
||||
<InputLabel htmlFor='invoice-edit-client-notes' className='cursor-pointer'>
|
||||
Client Notes
|
||||
</InputLabel>
|
||||
<Switch id='invoice-edit-client-notes' />
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-6'>
|
||||
<InputLabel htmlFor='invoice-edit-payment-stub' className='cursor-pointer'>
|
||||
Payment Stub
|
||||
</InputLabel>
|
||||
<Switch id='invoice-edit-payment-stub' />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditActions
|
||||
@@ -0,0 +1,342 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { SyntheticEvent } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import InputAdornment from '@mui/material/InputAdornment'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Button from '@mui/material/Button'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { InvoiceType } from '@/types/apps/invoiceTypes'
|
||||
|
||||
// Component Imports
|
||||
import Logo from '@components/layout/shared/Logo'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
// Styled Component Imports
|
||||
import AppReactDatepicker from '@/libs/styles/AppReactDatepicker'
|
||||
|
||||
const EditCard = ({ invoiceData, id, data }: { invoiceData?: InvoiceType; id: string; data?: InvoiceType[] }) => {
|
||||
// States
|
||||
const [selectData, setSelectData] = useState<InvoiceType | null>(data?.[0] || null)
|
||||
const [count, setCount] = useState(1)
|
||||
const [issueDate, setIssueDate] = useState(new Date(invoiceData?.issuedDate ?? ''))
|
||||
const [dueDate, setDueDate] = useState(new Date(invoiceData?.dueDate ?? ''))
|
||||
|
||||
// Hooks
|
||||
const isBelowMdScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'))
|
||||
|
||||
const deleteForm = (e: SyntheticEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
// @ts-ignore
|
||||
e.target.closest('.repeater-item').remove()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className='sm:!p-12'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='p-6 rounded bg-actionHover'>
|
||||
<div className='flex justify-between gap-4 flex-col sm:flex-row'>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<Logo />
|
||||
</div>
|
||||
<div>
|
||||
<Typography color='text.primary'>Office 149, 450 South Brand Brooklyn</Typography>
|
||||
<Typography color='text.primary'>San Diego County, CA 91905, USA</Typography>
|
||||
<Typography color='text.primary'>+1 (123) 456 7891, +44 (876) 543 2198</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography variant='h5' className='min-is-[95px]'>
|
||||
Invoice
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
value={id}
|
||||
slotProps={{
|
||||
input: {
|
||||
disabled: true,
|
||||
startAdornment: <InputAdornment position='start'>#</InputAdornment>
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Typography className='min-is-[95px] mie-4' color='text.primary'>
|
||||
Date Issued:
|
||||
</Typography>
|
||||
<AppReactDatepicker
|
||||
boxProps={{ className: 'is-full' }}
|
||||
selected={issueDate}
|
||||
id='payment-date'
|
||||
onChange={(date: Date | null) => date !== null && setIssueDate(date)}
|
||||
customInput={<CustomTextField fullWidth />}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Typography className='min-is-[95px] mie-4' color='text.primary'>
|
||||
Date Due:
|
||||
</Typography>
|
||||
<AppReactDatepicker
|
||||
boxProps={{ className: 'is-full' }}
|
||||
selected={dueDate}
|
||||
id='payment-date'
|
||||
onChange={(date: Date | null) => date !== null && setDueDate(date)}
|
||||
customInput={<CustomTextField fullWidth />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='flex justify-between flex-col gap-4 flex-wrap sm:flex-row'>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Invoice To:
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
select
|
||||
className='is-1/2 min-is-[220px] sm:is-auto'
|
||||
value={selectData?.id}
|
||||
onChange={e => {
|
||||
setSelectData(data?.slice(0, 5).filter(item => item.id === e.target.value)[0] || null)
|
||||
}}
|
||||
>
|
||||
{data?.slice(0, 5).map((invoice: InvoiceType, index) => (
|
||||
<MenuItem key={index} value={invoice.id}>
|
||||
{invoice.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</CustomTextField>
|
||||
<div>
|
||||
<Typography>{selectData?.name}</Typography>
|
||||
<Typography>{selectData?.company}</Typography>
|
||||
<Typography>{selectData?.address}</Typography>
|
||||
<Typography>{selectData?.contact}</Typography>
|
||||
<Typography>{selectData?.companyEmail}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Bill To:
|
||||
</Typography>
|
||||
<div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Total Due:</Typography>
|
||||
<Typography>$12,110.55</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Bank name:</Typography>
|
||||
<Typography>American Bank</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Country:</Typography>
|
||||
<Typography>United States</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>IBAN:</Typography>
|
||||
<Typography>ETD95476213874685</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>SWIFT code:</Typography>
|
||||
<Typography>BR91905</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
{Array.from(Array(count).keys()).map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={classnames('repeater-item flex relative mbe-4 border rounded', {
|
||||
'mbs-8': !isBelowMdScreen,
|
||||
'!mbs-14': index !== 0 && !isBelowMdScreen,
|
||||
'gap-5': isBelowMdScreen
|
||||
})}
|
||||
>
|
||||
<Grid container spacing={5} className='m-0 p-5'>
|
||||
<Grid size={{ xs: 12, md: 5, lg: 6 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8' color='text.primary'>
|
||||
Item
|
||||
</Typography>
|
||||
<CustomTextField select fullWidth defaultValue='App Design' className='mbe-5'>
|
||||
<MenuItem value='App Design'>App Design</MenuItem>
|
||||
<MenuItem value='App Customization'>App Customization</MenuItem>
|
||||
<MenuItem value='ABC Template'>ABC Template</MenuItem>
|
||||
<MenuItem value='App Development'>App Development</MenuItem>
|
||||
</CustomTextField>
|
||||
<CustomTextField rows={2} fullWidth multiline defaultValue='Customization & Bug Fixes' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 3, lg: 2 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8' color='text.primary'>
|
||||
Cost
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
{...(isBelowMdScreen && { fullWidth: true })}
|
||||
type='number'
|
||||
placeholder='24'
|
||||
defaultValue='24'
|
||||
className='mbe-5'
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: { min: 0 }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className='flex flex-col'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
Discount:
|
||||
</Typography>
|
||||
<div className='flex gap-2'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
0%
|
||||
</Typography>
|
||||
<Tooltip title='Tax 1' placement='top'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
0%
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title='Tax 2' placement='top'>
|
||||
<Typography component='span' color='text.primary'>
|
||||
0%
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 2 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8' color='text.primary'>
|
||||
Hours
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
{...(isBelowMdScreen && { fullWidth: true })}
|
||||
type='number'
|
||||
placeholder='1'
|
||||
defaultValue='1'
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: { min: 0 }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 2 }}>
|
||||
<Typography className='font-medium md:absolute md:-top-8' color='text.primary'>
|
||||
Price
|
||||
</Typography>
|
||||
<Typography>$24.00</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<div className='flex flex-col justify-start border-is'>
|
||||
<IconButton size='small' onClick={deleteForm}>
|
||||
<i className='tabler-x text-2xl text-actionActive' />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Button
|
||||
size='small'
|
||||
variant='contained'
|
||||
onClick={() => setCount(count + 1)}
|
||||
startIcon={<i className='tabler-plus' />}
|
||||
>
|
||||
Add Item
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='flex justify-between flex-col gap-4 sm:flex-row'>
|
||||
<div className='flex flex-col gap-4 order-2 sm:order-[unset]'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Salesperson:
|
||||
</Typography>
|
||||
<CustomTextField defaultValue='Tommy Shelby' />
|
||||
</div>
|
||||
<CustomTextField defaultValue='Thanks for your business' />
|
||||
</div>
|
||||
<div className='min-is-[200px]'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Subtotal:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$1800
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Discount:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$28
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Tax:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
21%
|
||||
</Typography>
|
||||
</div>
|
||||
<Divider className='mlb-2' />
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Total:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$1690
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<InputLabel htmlFor='invoice-note' className='inline-flex mbe-1 text-textPrimary'>
|
||||
Note:
|
||||
</InputLabel>
|
||||
<CustomTextField
|
||||
id='invoice-note'
|
||||
rows={2}
|
||||
fullWidth
|
||||
multiline
|
||||
className='border rounded'
|
||||
defaultValue='It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance
|
||||
projects. Thank You!'
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditCard
|
||||
@@ -0,0 +1,84 @@
|
||||
'use client'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Avatar from '@mui/material/Avatar'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Vars
|
||||
const data = [
|
||||
{
|
||||
title: 24,
|
||||
subtitle: 'Clients',
|
||||
icon: 'tabler-user'
|
||||
},
|
||||
{
|
||||
title: 165,
|
||||
subtitle: 'Invoices',
|
||||
icon: 'tabler-file-invoice'
|
||||
},
|
||||
{
|
||||
title: '$2.46k',
|
||||
subtitle: 'Paid',
|
||||
icon: 'tabler-checks'
|
||||
},
|
||||
{
|
||||
title: '$876',
|
||||
subtitle: 'Unpaid',
|
||||
icon: 'tabler-circle-off'
|
||||
}
|
||||
]
|
||||
|
||||
const InvoiceCard = () => {
|
||||
// Hooks
|
||||
const isBelowMdScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'))
|
||||
const isBelowSmScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'))
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Grid container spacing={6}>
|
||||
{data.map((item, index) => (
|
||||
<Grid
|
||||
size={{ xs: 12, sm: 6, md: 3 }}
|
||||
key={index}
|
||||
className={classnames({
|
||||
'[&:nth-of-type(odd)>div]:pie-6 [&:nth-of-type(odd)>div]:border-ie':
|
||||
isBelowMdScreen && !isBelowSmScreen,
|
||||
'[&:not(:last-child)>div]:pie-6 [&:not(:last-child)>div]:border-ie': !isBelowMdScreen
|
||||
})}
|
||||
>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex flex-col'>
|
||||
<Typography variant='h4'>{item.title}</Typography>
|
||||
<Typography>{item.subtitle}</Typography>
|
||||
</div>
|
||||
<Avatar variant='rounded' className='is-[42px] bs-[42px]'>
|
||||
<i className={classnames(item.icon, 'text-[26px]')} />
|
||||
</Avatar>
|
||||
</div>
|
||||
{isBelowMdScreen && !isBelowSmScreen && index < data.length - 2 && (
|
||||
<Divider
|
||||
className={classnames('mbs-6', {
|
||||
'mie-6': index % 2 === 0
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{isBelowSmScreen && index < data.length - 1 && <Divider className='mbs-6' />}
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default InvoiceCard
|
||||
@@ -0,0 +1,463 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Button from '@mui/material/Button'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Checkbox from '@mui/material/Checkbox'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import TablePagination from '@mui/material/TablePagination'
|
||||
import type { TextFieldProps } from '@mui/material/TextField'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import { rankItem } from '@tanstack/match-sorter-utils'
|
||||
import {
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
getFilteredRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFacetedMinMaxValues,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel
|
||||
} from '@tanstack/react-table'
|
||||
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
|
||||
import type { RankingInfo } from '@tanstack/match-sorter-utils'
|
||||
|
||||
// Type Imports
|
||||
import type { ThemeColor } from '@core/types'
|
||||
import type { InvoiceType } from '@/types/apps/invoiceTypes'
|
||||
import type { Locale } from '@configs/i18n'
|
||||
|
||||
// Component Imports
|
||||
import OptionMenu from '@core/components/option-menu'
|
||||
import CustomAvatar from '@core/components/mui/Avatar'
|
||||
import TablePaginationComponent from '@components/TablePaginationComponent'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
// Util Imports
|
||||
import { getInitials } from '@/utils/getInitials'
|
||||
import { getLocalizedUrl } from '@/utils/i18n'
|
||||
|
||||
// Style Imports
|
||||
import tableStyles from '@core/styles/table.module.css'
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface FilterFns {
|
||||
fuzzy: FilterFn<unknown>
|
||||
}
|
||||
interface FilterMeta {
|
||||
itemRank: RankingInfo
|
||||
}
|
||||
}
|
||||
|
||||
type InvoiceTypeWithAction = InvoiceType & {
|
||||
action?: string
|
||||
}
|
||||
|
||||
type InvoiceStatusObj = {
|
||||
[key: string]: {
|
||||
icon: string
|
||||
color: ThemeColor
|
||||
}
|
||||
}
|
||||
|
||||
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
|
||||
// Rank the item
|
||||
const itemRank = rankItem(row.getValue(columnId), value)
|
||||
|
||||
// Store the itemRank info
|
||||
addMeta({
|
||||
itemRank
|
||||
})
|
||||
|
||||
// Return if the item should be filtered in/out
|
||||
return itemRank.passed
|
||||
}
|
||||
|
||||
const DebouncedInput = ({
|
||||
value: initialValue,
|
||||
onChange,
|
||||
debounce = 500,
|
||||
...props
|
||||
}: {
|
||||
value: string | number
|
||||
onChange: (value: string | number) => void
|
||||
debounce?: number
|
||||
} & Omit<TextFieldProps, 'onChange'>) => {
|
||||
// States
|
||||
const [value, setValue] = useState(initialValue)
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue)
|
||||
}, [initialValue])
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
onChange(value)
|
||||
}, debounce)
|
||||
|
||||
return () => clearTimeout(timeout)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value])
|
||||
|
||||
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
|
||||
}
|
||||
|
||||
// Vars
|
||||
const invoiceStatusObj: InvoiceStatusObj = {
|
||||
Sent: { color: 'secondary', icon: 'tabler-send-2' },
|
||||
Paid: { color: 'success', icon: 'tabler-check' },
|
||||
Draft: { color: 'primary', icon: 'tabler-mail' },
|
||||
'Partial Payment': { color: 'warning', icon: 'tabler-chart-pie-2' },
|
||||
'Past Due': { color: 'error', icon: 'tabler-alert-circle' },
|
||||
Downloaded: { color: 'info', icon: 'tabler-arrow-down' }
|
||||
}
|
||||
|
||||
// Column Definitions
|
||||
const columnHelper = createColumnHelper<InvoiceTypeWithAction>()
|
||||
|
||||
const InvoiceListTable = ({ invoiceData }: { invoiceData?: InvoiceType[] }) => {
|
||||
// States
|
||||
const [status, setStatus] = useState<InvoiceType['invoiceStatus']>('')
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [data, setData] = useState(...[invoiceData])
|
||||
const [filteredData, setFilteredData] = useState(data)
|
||||
const [globalFilter, setGlobalFilter] = useState('')
|
||||
|
||||
// Hooks
|
||||
const { lang: locale } = useParams()
|
||||
|
||||
const columns = useMemo<ColumnDef<InvoiceTypeWithAction, any>[]>(
|
||||
() => [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
{...{
|
||||
checked: table.getIsAllRowsSelected(),
|
||||
indeterminate: table.getIsSomeRowsSelected(),
|
||||
onChange: table.getToggleAllRowsSelectedHandler()
|
||||
}}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
{...{
|
||||
checked: row.getIsSelected(),
|
||||
disabled: !row.getCanSelect(),
|
||||
indeterminate: row.getIsSomeSelected(),
|
||||
onChange: row.getToggleSelectedHandler()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
columnHelper.accessor('id', {
|
||||
header: '#',
|
||||
cell: ({ row }) => (
|
||||
<Typography
|
||||
component={Link}
|
||||
href={getLocalizedUrl(`/apps/invoice/preview/${row.original.id}`, locale as Locale)}
|
||||
color='primary.main'
|
||||
>{`#${row.original.id}`}</Typography>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('invoiceStatus', {
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
<Typography variant='body2' component='span' className='text-inherit'>
|
||||
{row.original.invoiceStatus}
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography variant='body2' component='span' className='text-inherit'>
|
||||
Balance:
|
||||
</Typography>{' '}
|
||||
{row.original.balance}
|
||||
<br />
|
||||
<Typography variant='body2' component='span' className='text-inherit'>
|
||||
Due Date:
|
||||
</Typography>{' '}
|
||||
{row.original.dueDate}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CustomAvatar skin='light' color={invoiceStatusObj[row.original.invoiceStatus].color} size={28}>
|
||||
<i className={classnames('bs-4 is-4', invoiceStatusObj[row.original.invoiceStatus].icon)} />
|
||||
</CustomAvatar>
|
||||
</Tooltip>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('name', {
|
||||
header: 'Client',
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center gap-3'>
|
||||
{getAvatar({ avatar: row.original.avatar, name: row.original.name })}
|
||||
<div className='flex flex-col'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
{row.original.name}
|
||||
</Typography>
|
||||
<Typography variant='body2'>{row.original.companyEmail}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('total', {
|
||||
header: 'Total',
|
||||
cell: ({ row }) => <Typography>{`$${row.original.total}`}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('issuedDate', {
|
||||
header: 'Issued Date',
|
||||
cell: ({ row }) => <Typography>{row.original.issuedDate}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('balance', {
|
||||
header: 'Balance',
|
||||
cell: ({ row }) => {
|
||||
return row.original.balance === 0 ? (
|
||||
<Chip label='Paid' color='success' size='small' variant='tonal' />
|
||||
) : (
|
||||
<Typography color='text.primary'>{row.original.balance}</Typography>
|
||||
)
|
||||
}
|
||||
}),
|
||||
columnHelper.accessor('action', {
|
||||
header: 'Action',
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center'>
|
||||
<IconButton onClick={() => setData(data?.filter(invoice => invoice.id !== row.original.id))}>
|
||||
<i className='tabler-trash text-textSecondary' />
|
||||
</IconButton>
|
||||
<IconButton>
|
||||
<Link
|
||||
href={getLocalizedUrl(`/apps/invoice/preview/${row.original.id}`, locale as Locale)}
|
||||
className='flex'
|
||||
>
|
||||
<i className='tabler-eye text-textSecondary' />
|
||||
</Link>
|
||||
</IconButton>
|
||||
<OptionMenu
|
||||
iconButtonProps={{ size: 'medium' }}
|
||||
iconClassName='text-textSecondary'
|
||||
options={[
|
||||
{
|
||||
text: 'Download',
|
||||
icon: 'tabler-download',
|
||||
menuItemProps: { className: 'flex items-center gap-2 text-textSecondary' }
|
||||
},
|
||||
{
|
||||
text: 'Edit',
|
||||
icon: 'tabler-pencil',
|
||||
href: getLocalizedUrl(`/apps/invoice/edit/${row.original.id}`, locale as Locale),
|
||||
linkProps: {
|
||||
className: 'flex items-center is-full plb-2 pli-4 gap-2 text-textSecondary'
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Duplicate',
|
||||
icon: 'tabler-copy',
|
||||
menuItemProps: { className: 'flex items-center gap-2 text-textSecondary' }
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
enableSorting: false
|
||||
})
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[data, filteredData]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: filteredData as InvoiceType[],
|
||||
columns,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter
|
||||
},
|
||||
state: {
|
||||
rowSelection,
|
||||
globalFilter
|
||||
},
|
||||
initialState: {
|
||||
pagination: {
|
||||
pageSize: 10
|
||||
}
|
||||
},
|
||||
enableRowSelection: true, //enable row selection for all rows
|
||||
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
|
||||
globalFilterFn: fuzzyFilter,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onGlobalFilterChange: setGlobalFilter,
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
getFacetedMinMaxValues: getFacetedMinMaxValues()
|
||||
})
|
||||
|
||||
const getAvatar = (params: Pick<InvoiceType, 'avatar' | 'name'>) => {
|
||||
const { avatar, name } = params
|
||||
|
||||
if (avatar) {
|
||||
return <CustomAvatar src={avatar} skin='light' size={34} />
|
||||
} else {
|
||||
return (
|
||||
<CustomAvatar skin='light' size={34}>
|
||||
{getInitials(name as string)}
|
||||
</CustomAvatar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const filteredData = data?.filter(invoice => {
|
||||
if (status && invoice.invoiceStatus.toLowerCase().replace(/\s+/g, '-') !== status) return false
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
setFilteredData(filteredData)
|
||||
}, [status, data, setFilteredData])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className='flex justify-between flex-col items-start md:items-center md:flex-row gap-4'>
|
||||
<div className='flex flex-col sm:flex-row items-center justify-between gap-4 is-full sm:is-auto'>
|
||||
<div className='flex items-center gap-2 is-full sm:is-auto'>
|
||||
<Typography className='hidden sm:block'>Show</Typography>
|
||||
<CustomTextField
|
||||
select
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={e => table.setPageSize(Number(e.target.value))}
|
||||
className='is-[70px] max-sm:is-full'
|
||||
>
|
||||
<MenuItem value='10'>10</MenuItem>
|
||||
<MenuItem value='25'>25</MenuItem>
|
||||
<MenuItem value='50'>50</MenuItem>
|
||||
</CustomTextField>
|
||||
</div>
|
||||
<Button
|
||||
variant='contained'
|
||||
component={Link}
|
||||
startIcon={<i className='tabler-plus' />}
|
||||
href={getLocalizedUrl('apps/invoice/add', locale as Locale)}
|
||||
className='max-sm:is-full'
|
||||
>
|
||||
Create Invoice
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex max-sm:flex-col max-sm:is-full sm:items-center gap-4'>
|
||||
<DebouncedInput
|
||||
value={globalFilter ?? ''}
|
||||
onChange={value => setGlobalFilter(String(value))}
|
||||
placeholder='Search Invoice'
|
||||
className='max-sm:is-full sm:is-[250px]'
|
||||
/>
|
||||
<CustomTextField
|
||||
select
|
||||
id='select-status'
|
||||
value={status}
|
||||
onChange={e => setStatus(e.target.value)}
|
||||
className='max-sm:is-full sm:is-[160px]'
|
||||
slotProps={{
|
||||
select: { displayEmpty: true }
|
||||
}}
|
||||
>
|
||||
<MenuItem value=''>Invoice Status</MenuItem>
|
||||
<MenuItem value='downloaded'>Downloaded</MenuItem>
|
||||
<MenuItem value='draft'>Draft</MenuItem>
|
||||
<MenuItem value='paid'>Paid</MenuItem>
|
||||
<MenuItem value='partial-payment'>Partial Payment</MenuItem>
|
||||
<MenuItem value='past-due'>Past Due</MenuItem>
|
||||
<MenuItem value='sent'>Sent</MenuItem>
|
||||
</CustomTextField>
|
||||
</div>
|
||||
</CardContent>
|
||||
<div className='overflow-x-auto'>
|
||||
<table className={tableStyles.table}>
|
||||
<thead>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map(header => (
|
||||
<th key={header.id}>
|
||||
{header.isPlaceholder ? null : (
|
||||
<>
|
||||
<div
|
||||
className={classnames({
|
||||
'flex items-center': header.column.getIsSorted(),
|
||||
'cursor-pointer select-none': header.column.getCanSort()
|
||||
})}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{{
|
||||
asc: <i className='tabler-chevron-up text-xl' />,
|
||||
desc: <i className='tabler-chevron-down text-xl' />
|
||||
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
{table.getFilteredRowModel().rows.length === 0 ? (
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
|
||||
No data available
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
) : (
|
||||
<tbody>
|
||||
{table
|
||||
.getRowModel()
|
||||
.rows.slice(0, table.getState().pagination.pageSize)
|
||||
.map(row => {
|
||||
return (
|
||||
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
|
||||
{row.getVisibleCells().map(cell => (
|
||||
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
)}
|
||||
</table>
|
||||
</div>
|
||||
<TablePagination
|
||||
component={() => <TablePaginationComponent table={table} />}
|
||||
count={table.getFilteredRowModel().rows.length}
|
||||
rowsPerPage={table.getState().pagination.pageSize}
|
||||
page={table.getState().pagination.pageIndex}
|
||||
onPageChange={(_, page) => {
|
||||
table.setPageIndex(page)
|
||||
}}
|
||||
onRowsPerPageChange={e => table.setPageSize(Number(e.target.value))}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default InvoiceListTable
|
||||
@@ -0,0 +1,24 @@
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Type Imports
|
||||
import type { InvoiceType } from '@/types/apps/invoiceTypes'
|
||||
|
||||
// Component Imports
|
||||
import InvoiceListTable from './InvoiceListTable'
|
||||
import InvoiceCard from './InvoiceCard'
|
||||
|
||||
const InvoiceList = ({ invoiceData }: { invoiceData?: InvoiceType[] }) => {
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<InvoiceCard />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<InvoiceListTable invoiceData={invoiceData} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default InvoiceList
|
||||
@@ -0,0 +1,80 @@
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Button from '@mui/material/Button'
|
||||
|
||||
// Type Imports
|
||||
import type { Locale } from '@configs/i18n'
|
||||
|
||||
// Component Imports
|
||||
import AddPaymentDrawer from '@views/apps/invoice/shared/AddPaymentDrawer'
|
||||
import SendInvoiceDrawer from '@views/apps/invoice/shared/SendInvoiceDrawer'
|
||||
|
||||
// Util Imports
|
||||
import { getLocalizedUrl } from '@/utils/i18n'
|
||||
|
||||
const PreviewActions = ({ id, onButtonClick }: { id: string; onButtonClick: () => void }) => {
|
||||
// States
|
||||
const [paymentDrawerOpen, setPaymentDrawerOpen] = useState(false)
|
||||
const [sendDrawerOpen, setSendDrawerOpen] = useState(false)
|
||||
|
||||
// Hooks
|
||||
const { lang: locale } = useParams()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col gap-4'>
|
||||
<Button
|
||||
fullWidth
|
||||
variant='contained'
|
||||
className='capitalize'
|
||||
startIcon={<i className='tabler-send' />}
|
||||
onClick={() => setSendDrawerOpen(true)}
|
||||
>
|
||||
Send Invoice
|
||||
</Button>
|
||||
<Button fullWidth color='secondary' variant='tonal' className='capitalize'>
|
||||
Download
|
||||
</Button>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button fullWidth color='secondary' variant='tonal' className='capitalize' onClick={onButtonClick}>
|
||||
Print
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
component={Link}
|
||||
color='secondary'
|
||||
variant='tonal'
|
||||
className='capitalize'
|
||||
href={getLocalizedUrl(`/apps/invoice/edit/${id}`, locale as Locale)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
fullWidth
|
||||
color='success'
|
||||
variant='contained'
|
||||
className='capitalize'
|
||||
onClick={() => setPaymentDrawerOpen(true)}
|
||||
startIcon={<i className='tabler-currency-dollar' />}
|
||||
>
|
||||
Add Payment
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AddPaymentDrawer open={paymentDrawerOpen} handleClose={() => setPaymentDrawerOpen(false)} />
|
||||
<SendInvoiceDrawer open={sendDrawerOpen} handleClose={() => setSendDrawerOpen(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PreviewActions
|
||||
@@ -0,0 +1,219 @@
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Divider from '@mui/material/Divider'
|
||||
|
||||
// Type Imports
|
||||
import type { InvoiceType } from '@/types/apps/invoiceTypes'
|
||||
|
||||
// Component Imports
|
||||
import Logo from '@components/layout/shared/Logo'
|
||||
|
||||
// Style Imports
|
||||
import tableStyles from '@core/styles/table.module.css'
|
||||
import './print.css'
|
||||
|
||||
// Vars
|
||||
const data = [
|
||||
{
|
||||
Item: 'Premium Branding Package',
|
||||
Description: 'Branding & Promotion',
|
||||
Hours: 48,
|
||||
Qty: 1,
|
||||
Total: '$32'
|
||||
},
|
||||
{
|
||||
Item: 'Social Media',
|
||||
Description: 'Social media templates',
|
||||
Hours: 42,
|
||||
Qty: 1,
|
||||
Total: '$28'
|
||||
},
|
||||
{
|
||||
Item: 'Web Design',
|
||||
Description: 'Web designing package',
|
||||
Hours: 46,
|
||||
Qty: 1,
|
||||
Total: '$24'
|
||||
},
|
||||
{
|
||||
Item: 'SEO',
|
||||
Description: 'Search engine optimization',
|
||||
Hours: 40,
|
||||
Qty: 1,
|
||||
Total: '$22'
|
||||
}
|
||||
]
|
||||
|
||||
const PreviewCard = ({ invoiceData, id }: { invoiceData?: InvoiceType; id: string }) => {
|
||||
return (
|
||||
<Card className='previewCard'>
|
||||
<CardContent className='sm:!p-12'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='p-6 bg-actionHover rounded'>
|
||||
<div className='flex justify-between gap-y-4 flex-col sm:flex-row'>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<Logo />
|
||||
</div>
|
||||
<div>
|
||||
<Typography color='text.primary'>Office 149, 450 South Brand Brooklyn</Typography>
|
||||
<Typography color='text.primary'>San Diego County, CA 91905, USA</Typography>
|
||||
<Typography color='text.primary'>+1 (123) 456 7891, +44 (876) 543 2198</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<Typography variant='h5'>{`Invoice #${id}`}</Typography>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Typography color='text.primary'>{`Date Issued: ${invoiceData?.issuedDate}`}</Typography>
|
||||
<Typography color='text.primary'>{`Date Due: ${invoiceData?.dueDate}`}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Invoice To:
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography>{invoiceData?.name}</Typography>
|
||||
<Typography>{invoiceData?.company}</Typography>
|
||||
<Typography>{invoiceData?.address}</Typography>
|
||||
<Typography>{invoiceData?.contact}</Typography>
|
||||
<Typography>{invoiceData?.companyEmail}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Bill To:
|
||||
</Typography>
|
||||
<div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Total Due:</Typography>
|
||||
<Typography>$12,110.55</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Bank name:</Typography>
|
||||
<Typography>American Bank</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>Country:</Typography>
|
||||
<Typography>United States</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>IBAN:</Typography>
|
||||
<Typography>ETD95476213874685</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Typography className='min-is-[100px]'>SWIFT code:</Typography>
|
||||
<Typography>BR91905</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='overflow-x-auto border rounded'>
|
||||
<table className={tableStyles.table}>
|
||||
<thead className='border-bs-0'>
|
||||
<tr>
|
||||
<th className='!bg-transparent'>Item</th>
|
||||
<th className='!bg-transparent'>Description</th>
|
||||
<th className='!bg-transparent'>Hours</th>
|
||||
<th className='!bg-transparent'>Qty</th>
|
||||
<th className='!bg-transparent'>Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<Typography color='text.primary'>{item.Item}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>{item.Description}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>{item.Hours}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>{item.Qty}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>{item.Total}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<div className='flex justify-between flex-col gap-y-4 sm:flex-row'>
|
||||
<div className='flex flex-col gap-1 order-2 sm:order-[unset]'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Salesperson:
|
||||
</Typography>
|
||||
<Typography>Tommy Shelby</Typography>
|
||||
</div>
|
||||
<Typography>Thanks for your business</Typography>
|
||||
</div>
|
||||
<div className='min-is-[200px]'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Subtotal:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$1800
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Discount:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$28
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Tax:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
21%
|
||||
</Typography>
|
||||
</div>
|
||||
<Divider className='mlb-2' />
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography>Total:</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$1690
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Divider className='border-dashed' />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Typography>
|
||||
<Typography component='span' className='font-medium' color='text.primary'>
|
||||
Note:
|
||||
</Typography>{' '}
|
||||
It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance
|
||||
projects. Thank You!
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default PreviewCard
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Type Imports
|
||||
import type { InvoiceType } from '@/types/apps/invoiceTypes'
|
||||
|
||||
// Component Imports
|
||||
import PreviewActions from './PreviewActions'
|
||||
import PreviewCard from './PreviewCard'
|
||||
|
||||
const Preview = ({ invoiceData, id }: { invoiceData?: InvoiceType; id: string }) => {
|
||||
// Handle Print Button Click
|
||||
const handleButtonClick = () => {
|
||||
window.print()
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12, md: 9 }}>
|
||||
<PreviewCard invoiceData={invoiceData} id={id} />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 3 }}>
|
||||
<PreviewActions id={id} onButtonClick={handleButtonClick} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default Preview
|
||||
@@ -0,0 +1,31 @@
|
||||
@media print {
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
#__next :is(.sm\:\!p-12) {
|
||||
padding: 0rem !important;
|
||||
}
|
||||
[data-dark] {
|
||||
--mui-palette-text-primary: rgba(47, 43, 61, 0.9);
|
||||
--mui-palette-action-hover: rgba(47, 43, 61, 0.06);
|
||||
--mui-palette-text-secondary: rgba(47, 43, 61, 0.7);
|
||||
--mui-palette-divider: rgba(47, 43, 61, 0.12);
|
||||
}
|
||||
|
||||
/* Only show the .preview-card element when printing */
|
||||
.previewCard * {
|
||||
visibility: visible;
|
||||
}
|
||||
.previewCard {
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.previewCard {
|
||||
position: relative !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { FormEvent } from 'react'
|
||||
|
||||
// MUI Import
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import InputAdornment from '@mui/material/InputAdornment'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Button from '@mui/material/Button'
|
||||
import Divider from '@mui/material/Divider'
|
||||
|
||||
// Component Imports
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
// Styled Component Imports
|
||||
import AppReactDatepicker from '@/libs/styles/AppReactDatepicker'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
type FormDataType = {
|
||||
paymentDate: Date
|
||||
paymentMethod: string
|
||||
paymentAmount: number
|
||||
paymentNote: string
|
||||
}
|
||||
|
||||
// Vars
|
||||
const initialData: FormDataType = {
|
||||
paymentDate: new Date(),
|
||||
paymentMethod: 'select-method',
|
||||
paymentAmount: 500,
|
||||
paymentNote: ''
|
||||
}
|
||||
|
||||
const AddPaymentDrawer = ({ open, handleClose }: Props) => {
|
||||
// States
|
||||
const [formData, setFormData] = useState<FormDataType>(initialData)
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
handleClose()
|
||||
setFormData(initialData)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
handleClose()
|
||||
setFormData(initialData)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={open}
|
||||
anchor='right'
|
||||
variant='temporary'
|
||||
onClose={handleReset}
|
||||
ModalProps={{ keepMounted: true }}
|
||||
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
|
||||
>
|
||||
<div className='flex items-center justify-between plb-5 pli-6'>
|
||||
<Typography variant='h5'>Add New User</Typography>
|
||||
<IconButton size='small' onClick={handleReset}>
|
||||
<i className='tabler-x text-2xl text-textPrimary' />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className='p-6'>
|
||||
<form onSubmit={handleSubmit} className='flex flex-col gap-6'>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
id='invoice-balance'
|
||||
label='Invoice Balance'
|
||||
slotProps={{
|
||||
input: {
|
||||
disabled: true
|
||||
}
|
||||
}}
|
||||
defaultValue='5000.00'
|
||||
/>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
id='payment-amount'
|
||||
label='Payment Amount'
|
||||
type='number'
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: <InputAdornment position='start'>$</InputAdornment>
|
||||
}
|
||||
}}
|
||||
value={formData.paymentAmount}
|
||||
onChange={e => setFormData({ ...formData, paymentAmount: +e.target.value })}
|
||||
/>
|
||||
<AppReactDatepicker
|
||||
selected={formData.paymentDate}
|
||||
id='payment-date'
|
||||
onChange={(date: Date | null) => date !== null && setFormData({ ...formData, paymentDate: date })}
|
||||
customInput={<CustomTextField fullWidth label='Payment Date' />}
|
||||
/>
|
||||
<CustomTextField
|
||||
select
|
||||
label='Payment Method'
|
||||
id='payment-method-select'
|
||||
value={formData.paymentMethod}
|
||||
onChange={e => setFormData({ ...formData, paymentMethod: e.target.value as string })}
|
||||
>
|
||||
<MenuItem value='select-method' disabled>
|
||||
Select Payment Method
|
||||
</MenuItem>
|
||||
<MenuItem value='cash'>Cash</MenuItem>
|
||||
<MenuItem value='bank-transfer'>Bank Transfer</MenuItem>
|
||||
<MenuItem value='credit'>Credit</MenuItem>
|
||||
<MenuItem value='debit'>Debit</MenuItem>
|
||||
<MenuItem value='paypal'>Paypal</MenuItem>
|
||||
</CustomTextField>
|
||||
<CustomTextField
|
||||
rows={6}
|
||||
multiline
|
||||
fullWidth
|
||||
label='Internal Payment Note'
|
||||
placeholder='Internal Payment Note'
|
||||
value={formData.paymentNote}
|
||||
onChange={e => setFormData({ ...formData, paymentNote: e.target.value })}
|
||||
/>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button variant='contained' type='submit'>
|
||||
Send
|
||||
</Button>
|
||||
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddPaymentDrawer
|
||||
@@ -0,0 +1,127 @@
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { FormEvent } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Button from '@mui/material/Button'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Divider from '@mui/material/Divider'
|
||||
|
||||
// Component Imports
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
type FormDataType = {
|
||||
from: string
|
||||
to: string
|
||||
subject: string
|
||||
message: string
|
||||
}
|
||||
|
||||
// Vars
|
||||
const initialData: FormDataType = {
|
||||
from: 'shelbyComapny@email.com',
|
||||
to: 'qConsolidated@email.com',
|
||||
subject: 'Invoice of purchased Admin Templates',
|
||||
message: `Dear Queen Consolidated,
|
||||
|
||||
Thank you for your business, always a pleasure to work with you!
|
||||
|
||||
We have generated a new invoice in the amount of $95.59
|
||||
|
||||
We would appreciate payment of this invoice by 05/11/2019`
|
||||
}
|
||||
|
||||
const SendInvoiceDrawer = ({ open, handleClose }: Props) => {
|
||||
// States
|
||||
const [formData, setFormData] = useState<FormDataType>(initialData)
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
handleClose()
|
||||
setFormData(initialData)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
handleClose()
|
||||
setFormData(initialData)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={open}
|
||||
anchor='right'
|
||||
variant='temporary'
|
||||
onClose={handleReset}
|
||||
ModalProps={{ keepMounted: true }}
|
||||
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
|
||||
>
|
||||
<div className='flex items-center justify-between plb-5 pli-6'>
|
||||
<Typography variant='h5'>Send Invoice</Typography>
|
||||
<IconButton size='small' onClick={handleReset}>
|
||||
<i className='tabler-x text-2xl text-textPrimary' />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className='p-6'>
|
||||
<form onSubmit={handleSubmit} className='flex flex-col items-start gap-6'>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='From'
|
||||
variant='outlined'
|
||||
value={formData.from}
|
||||
onChange={e => setFormData({ ...formData, from: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='To'
|
||||
variant='outlined'
|
||||
value={formData.to}
|
||||
onChange={e => setFormData({ ...formData, to: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Subject'
|
||||
variant='outlined'
|
||||
value={formData.subject}
|
||||
onChange={e => setFormData({ ...formData, subject: e.target.value })}
|
||||
/>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Message'
|
||||
variant='outlined'
|
||||
multiline
|
||||
rows={10}
|
||||
value={formData.message}
|
||||
onChange={e => setFormData({ ...formData, message: e.target.value })}
|
||||
/>
|
||||
<Chip
|
||||
size='small'
|
||||
color='primary'
|
||||
variant='tonal'
|
||||
className='rounded'
|
||||
label='Invoice Attached'
|
||||
icon={<i className='tabler-link' />}
|
||||
/>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button variant='contained' color='primary' type='submit'>
|
||||
Send
|
||||
</Button>
|
||||
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default SendInvoiceDrawer
|
||||
Reference in New Issue
Block a user