نسخ من RaghadAlkhous/RestaurantDash
499 أسطر
16 KiB
JavaScript
499 أسطر
16 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { useTheme } from '@mui/material/styles';
|
|
import {
|
|
useMediaQuery,
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableRow,
|
|
Chip,
|
|
Pagination,
|
|
Button,
|
|
Avatar,
|
|
TableContainer,
|
|
TextField,
|
|
IconButton,
|
|
} from '@mui/material';
|
|
|
|
import AssignmentIcon from '@mui/icons-material/Assignment';
|
|
import DriveFileRenameOutlineSharpIcon from '@mui/icons-material/DriveFileRenameOutlineSharp';
|
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
|
import CloseIcon from '@mui/icons-material/Close';
|
|
import { green } from '@mui/material/colors';
|
|
import jsPDF from 'jspdf';
|
|
import autoTable from 'jspdf-autotable';
|
|
import * as XLSX from 'xlsx';
|
|
import { saveAs } from 'file-saver';
|
|
import TuneIcon from '@mui/icons-material/Tune';
|
|
|
|
|
|
const TableView = ({ data = [], onAddNewProduct }) => {
|
|
const theme = useTheme();
|
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
|
const itemsPerPage = 5;
|
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [showSearch, setShowSearch] = useState(false);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
// فلترة البيانات بناءً على نص البحث (غير حساس لحالة الأحرف)
|
|
const filteredData = data.filter((item) =>
|
|
item.product.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
const pageCount = Math.ceil(filteredData.length / itemsPerPage);
|
|
|
|
const paginatedData = filteredData.slice(
|
|
(currentPage - 1) * itemsPerPage,
|
|
currentPage * itemsPerPage
|
|
);
|
|
|
|
const handleSearchChange = (e) => {
|
|
setSearchTerm(e.target.value);
|
|
setCurrentPage(1); // العودة للصفحة الأولى عند تغيير البحث
|
|
};
|
|
|
|
const handleExportPDF = () => {
|
|
const doc = new jsPDF();
|
|
doc.setFontSize(14);
|
|
doc.text('Top Selling Products', 14, 20);
|
|
|
|
const tableColumn = ['No.', 'Product', 'Sales', 'Amount', 'Price', 'Expiration', 'Status'];
|
|
const tableRows = data.map((row, i) => ([
|
|
i + 1,
|
|
row.product,
|
|
row.sales,
|
|
`$${row.amount}`,
|
|
`$${row.price}`,
|
|
row.expiration,
|
|
row.status
|
|
]));
|
|
|
|
autoTable(doc, {
|
|
startY: 30,
|
|
head: [tableColumn],
|
|
body: tableRows,
|
|
styles: { fontSize: 10 },
|
|
headStyles: { fillColor: [240, 240, 240] }
|
|
});
|
|
|
|
doc.save('top_selling_products.pdf');
|
|
};
|
|
|
|
const handleExportSpreadsheet = () => {
|
|
const wsData = [
|
|
['No.', 'Product', 'Sales', 'Amount', 'Price', 'Expiration', 'Status'],
|
|
...data.map((row, i) => [
|
|
i + 1,
|
|
row.product,
|
|
row.sales,
|
|
`$${row.amount}`,
|
|
`$${row.price}`,
|
|
row.expiration,
|
|
row.status
|
|
])
|
|
];
|
|
|
|
const worksheet = XLSX.utils.aoa_to_sheet(wsData);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Top Selling");
|
|
|
|
const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
|
|
const blob = new Blob([wbout], { type: 'application/octet-stream' });
|
|
saveAs(blob, 'top_selling_products.xlsx');
|
|
};
|
|
|
|
|
|
return (
|
|
<Paper
|
|
sx={{
|
|
backgroundColor: '#FFFFFF',
|
|
maxWidth: { xs: '90%', md: '100%' },
|
|
borderRadius: 2,
|
|
height: { xs: 'auto', md: '100vh' },
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
overflow: 'hidden',
|
|
boxShadow: theme.shadows[1],
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<Box
|
|
sx={{
|
|
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
|
mb: 3,
|
|
pl: { xs: 1, sm: 3 },
|
|
pt: 2,
|
|
pr: { xs: 1, sm: 3 },
|
|
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
|
gap: { xs: 2, sm: 0 }
|
|
}}
|
|
>
|
|
<Typography variant="h6" sx={{ fontSize: { xs: '1rem', sm: '1.25rem' } }}>
|
|
Table View
|
|
</Typography>
|
|
|
|
<Box
|
|
display="flex"
|
|
gap={1}
|
|
flexWrap={{ xs: 'wrap', sm: 'nowrap' }}
|
|
width={{ xs: '100%', sm: 'auto' }}
|
|
justifyContent={{ xs: 'space-between', sm: 'flex-end' }}
|
|
>
|
|
{/* زر إضافة منتج جديد */}
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={onAddNewProduct}
|
|
sx={{
|
|
textTransform: 'none',
|
|
borderRadius: '8px',
|
|
height: '40px',
|
|
width: { xs: '100%', sm: 'auto' },
|
|
fontSize: { xs: '12px', sm: '14px' },
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
|
|
Add New Product
|
|
|
|
</Button>
|
|
{/* زر تصدير جدول (Spreadsheet) */}
|
|
<Button
|
|
variant="contained"
|
|
sx={{
|
|
textTransform: 'none',
|
|
color: 'white',
|
|
backgroundColor: '#5F6868',
|
|
boxShadow: 'none',
|
|
borderRadius: '8px',
|
|
height: '40px',
|
|
width: { xs: '48%', sm: '140px', md: '166px' },
|
|
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
|
fontWeight: 600,
|
|
p: 0,
|
|
m: 0,
|
|
whiteSpace: 'nowrap',
|
|
minWidth: 'unset',
|
|
}}
|
|
onClick={handleExportSpreadsheet}
|
|
>
|
|
{isMobile ? 'Export' : 'Export Spreadsheet'}
|
|
</Button>
|
|
|
|
{/* زر PDF */}
|
|
<Button
|
|
variant="contained"
|
|
sx={{
|
|
textTransform: 'none',
|
|
color: 'white',
|
|
backgroundColor: theme.palette.primary.main,
|
|
boxShadow: 'none',
|
|
borderRadius: '8px',
|
|
height: '40px',
|
|
width: { xs: '48%', sm: '90px', md: '105px' },
|
|
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
|
fontWeight: 600,
|
|
p: 0,
|
|
m: 0,
|
|
whiteSpace: 'nowrap',
|
|
minWidth: 'unset',
|
|
}}
|
|
onClick={handleExportPDF}
|
|
>
|
|
Export PDF
|
|
</Button>
|
|
|
|
{/* زر الفلاتر */}
|
|
<Button
|
|
variant="outlined"
|
|
sx={{
|
|
textTransform: 'none',
|
|
color: '#667085',
|
|
borderColor: '#e0e0e0',
|
|
backgroundColor: '#fff',
|
|
borderRadius: '8px',
|
|
height: '40px',
|
|
width: { xs: '100%', sm: '120px', md: '99px' },
|
|
fontSize: { xs: '12px', sm: '13px', md: '14px' },
|
|
fontWeight: 600,
|
|
p: 0,
|
|
m: 0,
|
|
whiteSpace: 'nowrap',
|
|
minWidth: 'unset',
|
|
}}
|
|
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
|
>
|
|
{isMobile ? '' : 'Filters'}
|
|
</Button>
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
{/* جدول البيانات */}
|
|
<TableContainer
|
|
sx={{
|
|
flexGrow: 1,
|
|
overflowX: 'auto',
|
|
maxHeight: { xs: '60vh', md: 'calc(100vh - 160px)' },
|
|
'&::-webkit-scrollbar': { height: 4 },
|
|
}}
|
|
>
|
|
<Table
|
|
size={isMobile ? 'small' : 'medium'}
|
|
sx={{
|
|
minWidth: 750,
|
|
'& .MuiTableCell-root': {
|
|
borderBottom: '1px solid #e0e0e0',
|
|
py: { xs: 0.5, sm: 1.5 },
|
|
},
|
|
'& thead .MuiTableCell-root': {
|
|
borderBottom: 'none',
|
|
fontWeight: 600,
|
|
fontSize: { xs: '11px', sm: '14px' },
|
|
px: { xs: 0.5, sm: 2 },
|
|
},
|
|
}}
|
|
>
|
|
<TableHead sx={{ backgroundColor: '#F0F1F3' }}>
|
|
<TableRow sx={{ height: { xs: '6vh', sm: '10vh' } }}>
|
|
<TableCell width="6%">No.</TableCell>
|
|
|
|
<TableCell width="20%" sx={{ position: 'relative' }}>
|
|
<Box
|
|
sx={{ display: 'flex', alignItems: 'center', gap: 1, cursor: 'pointer', userSelect: 'none' }}
|
|
onClick={() => setShowSearch(prev => !prev)}
|
|
>
|
|
<Typography>Product Name</Typography>
|
|
<ArrowDropDownIcon
|
|
fontSize="small"
|
|
sx={{
|
|
transform: showSearch ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
transition: 'transform 0.3s ease',
|
|
}}
|
|
/>
|
|
</Box>
|
|
{showSearch && (
|
|
<TextField
|
|
size="small"
|
|
variant="outlined"
|
|
placeholder="Search product..."
|
|
value={searchTerm}
|
|
onChange={handleSearchChange}
|
|
sx={{ mt: 1, width: '100%' }}
|
|
InputProps={{
|
|
endAdornment: searchTerm && (
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => setSearchTerm('')}
|
|
aria-label="Clear search"
|
|
>
|
|
<CloseIcon fontSize="small" />
|
|
</IconButton>
|
|
),
|
|
}}
|
|
/>
|
|
)}
|
|
</TableCell>
|
|
|
|
|
|
{!isMobile && <TableCell width="10%">Barcode</TableCell>}
|
|
<TableCell width="10%">Branch</TableCell>
|
|
{!isMobile && <TableCell width="10%">Expiration</TableCell>}
|
|
{!isMobile && <TableCell width="15%">Stock Quantity </TableCell>}
|
|
<TableCell width="15%">Consumption Status</TableCell>
|
|
<TableCell width="10%" align="center">
|
|
Action
|
|
</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
|
|
<TableBody
|
|
sx={{
|
|
'& .MuiTableCell-root': {
|
|
px: { xs: 2, sm: 3 },
|
|
},
|
|
}}
|
|
>
|
|
{paginatedData.length > 0 ? (
|
|
paginatedData.map((row, i) => (
|
|
<TableRow key={i} sx={{ height: { xs: '8vh', sm: '13vh' } }}>
|
|
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>
|
|
{(currentPage - 1) * itemsPerPage + i + 1}
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: { xs: 1, sm: 2 },
|
|
}}
|
|
>
|
|
<Avatar
|
|
sx={{
|
|
bgcolor: green[50],
|
|
width: { xs: 28, sm: 40 },
|
|
height: { xs: 28, sm: 40 },
|
|
}}
|
|
variant="rounded"
|
|
>
|
|
<AssignmentIcon
|
|
fontSize={isMobile ? 'small' : 'medium'}
|
|
/>
|
|
</Avatar>
|
|
<Box
|
|
sx={{
|
|
fontSize: { xs: '11px', sm: '14px' },
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
maxWidth: { xs: '80px', sm: '200px' },
|
|
}}
|
|
>
|
|
{row.product}
|
|
</Box>
|
|
</Box>
|
|
</TableCell>
|
|
|
|
{!isMobile && (
|
|
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>
|
|
{row.sales}
|
|
</TableCell>
|
|
)}
|
|
|
|
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>
|
|
${row.amount.toLocaleString()}
|
|
</TableCell>
|
|
|
|
{!isMobile && (
|
|
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>
|
|
${row.price}
|
|
</TableCell>
|
|
)}
|
|
|
|
{!isMobile && (
|
|
<TableCell sx={{ fontSize: { xs: '11px', sm: '14px' } }}>
|
|
{row.expiration}
|
|
</TableCell>
|
|
)}
|
|
|
|
<TableCell>
|
|
<Chip
|
|
label={isMobile ? row.status.substring(0, 3) : row.status}
|
|
size={isMobile ? 'small' : 'medium'}
|
|
sx={{
|
|
fontSize: { xs: '10px', sm: '14px' },
|
|
fontWeight: 600,
|
|
backgroundColor:
|
|
row.status === 'Published'
|
|
? '#E7F4EE'
|
|
: row.status === 'Low Stock'
|
|
? '#FDF1E8'
|
|
: row.status === 'Out of Stock'
|
|
? '#FFCDD2'
|
|
: '#E0E0E0',
|
|
color:
|
|
row.status === 'Published'
|
|
? '#0D894F'
|
|
: row.status === 'Low Stock'
|
|
? '#E46A11'
|
|
: row.status === 'Out of Stock'
|
|
? '#C62828'
|
|
: '#424242',
|
|
minWidth: { xs: '50px', sm: '100px' },
|
|
}}
|
|
/>
|
|
</TableCell>
|
|
|
|
<TableCell align="center">
|
|
<Button
|
|
variant="text"
|
|
size="small"
|
|
onClick={() => console.log('Edit', row)}
|
|
sx={{ minWidth: 0, padding: 0 }}
|
|
>
|
|
<DriveFileRenameOutlineSharpIcon
|
|
fontSize={isMobile ? 'small' : 'medium'}
|
|
sx={{ color: '#5F6868' }}
|
|
/>
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
) : (
|
|
<TableRow>
|
|
<TableCell colSpan={8} align="center" sx={{ py: 5 }}>
|
|
No products found.
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
{/* Pagination Footer */}
|
|
<Box
|
|
display="flex"
|
|
justifyContent="space-between"
|
|
alignItems="center"
|
|
p={{ xs: 1, sm: 2 }}
|
|
sx={{
|
|
position: 'sticky',
|
|
bottom: 0,
|
|
backgroundColor: theme.palette.background.paper,
|
|
borderTop: '1px solid #f0f0f0',
|
|
zIndex: 1,
|
|
}}
|
|
>
|
|
<Typography
|
|
variant="body2"
|
|
color="text.secondary"
|
|
sx={{
|
|
fontSize: { xs: '11px', sm: '14px' },
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
Showing{' '}
|
|
{(currentPage - 1) * itemsPerPage + 1} to{' '}
|
|
{Math.min(currentPage * itemsPerPage, filteredData.length)} of{' '}
|
|
{filteredData.length}
|
|
</Typography>
|
|
|
|
<Pagination
|
|
count={pageCount}
|
|
page={currentPage}
|
|
onChange={(event, value) => setCurrentPage(value)}
|
|
size={isMobile ? 'small' : 'medium'}
|
|
sx={{
|
|
'& .MuiPaginationItem-root': {
|
|
fontSize: { xs: '12px', sm: '14px' },
|
|
minWidth: { xs: 24, sm: 32 },
|
|
height: { xs: 24, sm: 32 },
|
|
backgroundColor: '#FFECE0',
|
|
color: theme.palette.primary.main,
|
|
borderRadius: '8px',
|
|
'&.Mui-selected': {
|
|
backgroundColor: theme.palette.primary.main,
|
|
color: '#fff',
|
|
},
|
|
'&:hover': {
|
|
backgroundColor: '#FFD6B5',
|
|
},
|
|
},
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
export default TableView;
|