first commit
This commit is contained in:
180
src/pages/invoice/InvoicePreview.jsx
Normal file
180
src/pages/invoice/InvoicePreview.jsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
Box, Card, Stack, Button, Typography, Chip, Divider, IconButton,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Grid,
|
||||
Dialog, DialogTitle, DialogContent, DialogActions, TextField
|
||||
} from '@mui/material';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined';
|
||||
import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined';
|
||||
|
||||
import PageHeader from '@/components/PageHeader';
|
||||
import Logo from '@/components/Logo';
|
||||
import { invoices, invoiceLineItems, tenants } from '@/data/mock';
|
||||
import { inr } from '@/utils/format';
|
||||
|
||||
export default function InvoicePreview() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const [payOpen, setPayOpen] = useState(false);
|
||||
|
||||
const invoice = invoices.find((i) => String(i.id) === String(id)) || invoices[0];
|
||||
const tenant = tenants[0];
|
||||
|
||||
const subTotal = invoiceLineItems.reduce((a, l) => a + l.amount, 0);
|
||||
const discount = Math.round(subTotal * 0.05);
|
||||
const taxable = subTotal - discount;
|
||||
const tax = Math.round(taxable * 0.18);
|
||||
const grandTotal = taxable + tax;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Invoice Details"
|
||||
breadcrumbs={[{ label: 'Invoice', to: '/invoice' }, { label: 'Details' }]}
|
||||
action={
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} alignItems="center">
|
||||
<IconButton onClick={() => navigate('/invoice')} sx={{ border: 1, borderColor: 'grey.300' }}>
|
||||
<ArrowBackIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Chip label={invoice.invoiceId} sx={{ bgcolor: 'warning.lighter', color: 'warning.dark', fontWeight: 600 }} />
|
||||
<Button variant="outlined" startIcon={<PaymentsOutlinedIcon />} onClick={() => setPayOpen(true)}>
|
||||
Update Payment
|
||||
</Button>
|
||||
<Button variant="contained" startIcon={<PrintOutlinedIcon />} onClick={() => window.print()}>
|
||||
Print
|
||||
</Button>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Card sx={{ width: '100%', maxWidth: 860, p: { xs: 3, sm: 5 } }}>
|
||||
{/* Top band */}
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} justifyContent="space-between" spacing={3}>
|
||||
<Box>
|
||||
<Logo />
|
||||
<Typography variant="subtitle2" sx={{ mt: 2, fontWeight: 700, color: 'grey.800' }}>From</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>Doormile Logistics Pvt. Ltd.</Typography>
|
||||
<Typography variant="body2" color="text.secondary">No. 7, Brigade Road</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Bengaluru, Karnataka 560001</Typography>
|
||||
<Typography variant="body2" color="text.secondary">GSTIN: 29ABCDE1234F1Z5</Typography>
|
||||
<Typography variant="body2" color="text.secondary">billing@doormile.in</Typography>
|
||||
</Box>
|
||||
<Box sx={{ textAlign: { sm: 'right' } }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 800, color: 'primary.main' }}>INVOICE</Typography>
|
||||
<Typography variant="subtitle2" sx={{ mt: 2, fontWeight: 700, color: 'grey.800' }}>To</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{tenant.name}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{tenant.contact}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{tenant.address}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{tenant.city} {tenant.postcode}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{tenant.email}</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
|
||||
{/* Invoice meta */}
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Typography variant="caption" color="text.secondary">Invoice No</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{invoice.invoiceId}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Typography variant="caption" color="text.secondary">Date</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{invoice.invoiceDate}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Typography variant="caption" color="text.secondary">Due Date</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{invoice.dueDate}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Typography variant="caption" color="text.secondary">Period</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{invoice.period}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Line items */}
|
||||
<TableContainer sx={{ mt: 3 }}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow sx={{ '& th': { bgcolor: 'grey.50', fontWeight: 700 } }}>
|
||||
<TableCell>S.No</TableCell>
|
||||
<TableCell>Particulars</TableCell>
|
||||
<TableCell>Unit</TableCell>
|
||||
<TableCell align="center">Quantity</TableCell>
|
||||
<TableCell align="right">Rate</TableCell>
|
||||
<TableCell align="right">Other Charges</TableCell>
|
||||
<TableCell align="right">Amount</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{invoiceLineItems.map((l, idx) => (
|
||||
<TableRow key={idx}>
|
||||
<TableCell>{idx + 1}</TableCell>
|
||||
<TableCell>{l.particulars}</TableCell>
|
||||
<TableCell>{l.unit}</TableCell>
|
||||
<TableCell align="center">{l.qty}</TableCell>
|
||||
<TableCell align="right">{inr(l.rate)}</TableCell>
|
||||
<TableCell align="right">{inr(l.other)}</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600 }}>{inr(l.amount)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Totals */}
|
||||
<Stack alignItems="flex-end" sx={{ mt: 3 }}>
|
||||
<Box sx={{ width: { xs: '100%', sm: 320 } }}>
|
||||
<Stack direction="row" justifyContent="space-between" sx={{ py: 0.75 }}>
|
||||
<Typography variant="body2" color="text.secondary">Sub Total</Typography>
|
||||
<Typography variant="body2">{inr(subTotal)}</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="space-between" sx={{ py: 0.75 }}>
|
||||
<Typography variant="body2" color="text.secondary">Discount (5%)</Typography>
|
||||
<Typography variant="body2">- {inr(discount)}</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="space-between" sx={{ py: 0.75 }}>
|
||||
<Typography variant="body2" color="text.secondary">Tax (18% GST)</Typography>
|
||||
<Typography variant="body2">{inr(tax)}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between" sx={{ py: 0.5 }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>Grand Total</Typography>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 800, color: 'primary.main' }}>{inr(grandTotal)}</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Notes + accent */}
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: 'grey.800' }}>Notes & Terms</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
|
||||
Payment is due within 15 days of the invoice date. Please make payments via NEFT/RTGS to the
|
||||
registered account. A 1.5% monthly interest applies to overdue balances. This is a
|
||||
computer-generated invoice and does not require a signature.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3, height: 4, borderRadius: 2, bgcolor: 'primary.main' }} />
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
{/* Update Payment Dialog */}
|
||||
<Dialog open={payOpen} onClose={() => setPayOpen(false)} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>Update Payment</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2.5} sx={{ mt: 1 }}>
|
||||
<TextField label="Reference No" type="number" fullWidth placeholder="Enter transaction reference" />
|
||||
<TextField label="Remarks" fullWidth multiline minRows={3} placeholder="Add a remark for this payment" />
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={() => setPayOpen(false)} color="inherit">Cancel</Button>
|
||||
<Button variant="contained" onClick={() => setPayOpen(false)}>Update</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user