Initial commit - restaurant dashboard
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import React, { useState } from 'react';
|
||||
import TableView from './TableView';
|
||||
|
||||
const initialProducts = [
|
||||
{
|
||||
product: 'Apple Watch',
|
||||
sales: 150,
|
||||
amount: 45000,
|
||||
price: 299,
|
||||
status: 'Published',
|
||||
expiration: '30 Days Left',
|
||||
},
|
||||
{
|
||||
product: 'Samsung Galaxy',
|
||||
sales: 90,
|
||||
amount: 36000,
|
||||
price: 400,
|
||||
status: 'Low Stock',
|
||||
expiration: '12 Days Left',
|
||||
},
|
||||
{
|
||||
product: 'Sony Headphones',
|
||||
sales: 60,
|
||||
amount: 18000,
|
||||
price: 299,
|
||||
status: 'Draft',
|
||||
expiration: '5 Days Left',
|
||||
},
|
||||
];
|
||||
|
||||
const InventoryTablePage = () => {
|
||||
const [products, setProducts] = useState(initialProducts);
|
||||
|
||||
const handleAddNewProduct = () => {
|
||||
alert('Navigate to Add Product page or open form here');
|
||||
};
|
||||
|
||||
return <TableView data={products} onAddNewProduct={handleAddNewProduct} />;
|
||||
};
|
||||
|
||||
export default InventoryTablePage;
|
||||
@@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
useMediaQuery
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
const NoProdectDash = () => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Header Section */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
||||
mb: 3,
|
||||
mt:10,
|
||||
pl: 1,
|
||||
pr: { xs: 1, sm: 3 },
|
||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
||||
gap: { xs: 2, sm: 0 }
|
||||
}}
|
||||
>
|
||||
|
||||
</Box>
|
||||
{/* Empty State Content */}
|
||||
<Card
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
p: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
{/* Image Placeholder */}
|
||||
<Box
|
||||
component="img"
|
||||
src="/images/compeoter.png"
|
||||
alt="No products available"
|
||||
sx={{
|
||||
width: isSmallScreen ? 150 : 200,
|
||||
height: 'auto',
|
||||
mb: 1,
|
||||
opacity: 0.8
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Title with colored "Sorry!" */}
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: { xs: '14px', md: '18px' },
|
||||
mb: 2,
|
||||
color: '#5F6868',
|
||||
whiteSpace: 'pre-line' // هذا يسمح بكسر السطر عند المسافات
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ fontWeight: 600, fontSize: '18px' }}>Sorry!</Box>{' '}
|
||||
There is no campaign available.
|
||||
Please initiate the {'\n'}creation of a new campaign.
|
||||
</Typography>
|
||||
|
||||
{/* Action Button */}
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
borderRadius: 2,
|
||||
color:'white',
|
||||
width:179
|
||||
}}
|
||||
>
|
||||
Add a Product
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoProdectDash;
|
||||
@@ -0,0 +1,434 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
styled,
|
||||
useTheme,
|
||||
MenuItem,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio as MuiRadio
|
||||
} from '@mui/material';
|
||||
import axios from 'axios';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
import TableView from './TableView';
|
||||
|
||||
const ProductEntry = ({ onAdd, onShowTable }) => {
|
||||
const theme = useTheme();
|
||||
const [profileImage, setProfileImage] = useState(null);
|
||||
const [fullName, setFullName] = useState('');
|
||||
const [username, setUsername] = useState('');
|
||||
const [bio, setBio] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [selectedBranch, setSelectedBranch] = useState('default');
|
||||
const [branches, setBranches] = useState([
|
||||
{ label: 'Damascus Branch', value: 'damascus' },
|
||||
{ label: 'Aleppo Branch', value: 'aleppo' },
|
||||
{ label: 'Homs Branch', value: 'homs' }
|
||||
]);
|
||||
const [expirationDate, setExpirationDate] = useState(null);
|
||||
const [consumptionStatus, setConsumptionStatus] = useState(false);
|
||||
const [showTable, setShowTable] = useState(false);
|
||||
const [products, setProducts] = useState([]);
|
||||
|
||||
// حالات جديدة للحفظ
|
||||
const [stocksLevel, setStocksLevel] = useState('');
|
||||
const [productCategory, setProductCategory] = useState('default');
|
||||
const [supplierName, setSupplierName] = useState('');
|
||||
const [supplierNumber, setSupplierNumber] = useState('');
|
||||
const [productPrice, setProductPrice] = useState('');
|
||||
const [phoneNumber, setPhoneNumber] = useState('');
|
||||
const [productCost, setProductCost] = useState('');
|
||||
|
||||
// قائمة فئات المنتجات
|
||||
const productCategories = [
|
||||
{ label: 'Food', value: 'food' },
|
||||
{ label: 'Beverages', value: 'beverages' },
|
||||
{ label: 'Electronics', value: 'electronics' },
|
||||
{ label: 'Clothing', value: 'clothing' },
|
||||
];
|
||||
const handleShowTable = () => {
|
||||
setShowTable(true);
|
||||
};
|
||||
useEffect(() => {
|
||||
axios.get('https://mocki.io/v1/0b9d4121-d7e7-42f4-b1d5-b14f82e35b69')
|
||||
.then((response) => {
|
||||
setBranches(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching branches:', error);
|
||||
});
|
||||
|
||||
const savedData = localStorage.getItem('productEntryData');
|
||||
if (savedData) {
|
||||
const data = JSON.parse(savedData);
|
||||
setFullName(data.fullName || '');
|
||||
setUsername(data.username || '');
|
||||
setEmail(data.email || '');
|
||||
setSelectedBranch(data.selectedBranch || 'default');
|
||||
setExpirationDate(data.expirationDate ? new Date(data.expirationDate) : null);
|
||||
setConsumptionStatus(data.consumptionStatus || false);
|
||||
setStocksLevel(data.stocksLevel || '');
|
||||
setProductCategory(data.productCategory || 'default');
|
||||
setSupplierName(data.supplierName || '');
|
||||
setSupplierNumber(data.supplierNumber || '');
|
||||
setProductPrice(data.productPrice || '');
|
||||
setPhoneNumber(data.phoneNumber || '');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const dataToSave = {
|
||||
fullName,
|
||||
username,
|
||||
email,
|
||||
selectedBranch,
|
||||
expirationDate: expirationDate ? expirationDate.toISOString() : null,
|
||||
consumptionStatus,
|
||||
stocksLevel,
|
||||
productCategory,
|
||||
supplierName,
|
||||
supplierNumber,
|
||||
productPrice,
|
||||
phoneNumber,
|
||||
};
|
||||
localStorage.setItem('productEntryData', JSON.stringify(dataToSave));
|
||||
}, [
|
||||
fullName,
|
||||
username,
|
||||
email,
|
||||
selectedBranch,
|
||||
expirationDate,
|
||||
consumptionStatus,
|
||||
stocksLevel,
|
||||
productCategory,
|
||||
supplierName,
|
||||
supplierNumber,
|
||||
productPrice,
|
||||
phoneNumber,
|
||||
]);
|
||||
|
||||
const handleImageUpload = (event) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
setProfileImage(URL.createObjectURL(event.target.files[0]));
|
||||
}
|
||||
};
|
||||
|
||||
const Input = styled('input')({
|
||||
display: 'none',
|
||||
});
|
||||
|
||||
const customInputStyle = {
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
borderColor: '#3f51b5',
|
||||
boxShadow: '0 0 0 2px rgba(63,81,181,0.1)',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
const renderField = (label, value, onChange, type = 'text', placeholder = '') => (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: { xs: '100%', sm: '45%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={customInputStyle}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const renderSelectField = (label, value, onChange, options = []) => (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: { xs: '100%', sm: '45%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
sx={customInputStyle}
|
||||
>
|
||||
<MenuItem value="default" disabled sx={{ color: '#9e9e9e' }}>
|
||||
Select {label}...
|
||||
</MenuItem>
|
||||
{options.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const handleSave = () => {
|
||||
// احصل على أكبر رقم منتج موجود في القائمة
|
||||
const maxProductNumber = products.length > 0
|
||||
? Math.max(...products.map(p => Number(p.username) || 0))
|
||||
: 0;
|
||||
|
||||
const newProductNumber = maxProductNumber + 1;
|
||||
|
||||
const newProduct = {
|
||||
fullName,
|
||||
username: newProductNumber.toString(), // هنا رقم المنتج يتزاد تلقائياً
|
||||
email,
|
||||
selectedBranch,
|
||||
expirationDate: expirationDate ? expirationDate.toISOString().split('T')[0] : '',
|
||||
consumptionStatus,
|
||||
stocksLevel,
|
||||
productCategory,
|
||||
supplierName,
|
||||
supplierNumber,
|
||||
productPrice,
|
||||
phoneNumber,
|
||||
productCost,
|
||||
};
|
||||
|
||||
// أضف المنتج الجديد لقائمة المنتجات
|
||||
setProducts(prev => [...prev, newProduct]);
|
||||
|
||||
// استدعي دالة onAdd إن كانت موجودة
|
||||
if (onAdd) {
|
||||
onAdd(newProduct);
|
||||
}
|
||||
|
||||
// إعادة تعيين الحقول (يمكنك إزالة إعادة تعيين username لأنه أصبح تلقائي)
|
||||
setFullName('');
|
||||
setEmail('');
|
||||
setSelectedBranch('default');
|
||||
setExpirationDate(null);
|
||||
setConsumptionStatus(false);
|
||||
setStocksLevel('');
|
||||
setProductCategory('default');
|
||||
setSupplierName('');
|
||||
setSupplierNumber('');
|
||||
setProductPrice('');
|
||||
setPhoneNumber('');
|
||||
setProductCost('');
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: '#FFFFFF',
|
||||
maxWidth: { xs: '90%', md: '100%' },
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{/* الحقول النصية */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
{/* السطر الأول: Product Name + Barcode Maker */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
{renderField('Product Name', fullName, (e) => setFullName(e.target.value), 'text', 'Pizza')}
|
||||
{renderField('Barcode Maker', email, (e) => setEmail(e.target.value), 'text', '12344')}
|
||||
</Box>
|
||||
|
||||
{/* السطر الثاني: Branch Select + Product Number */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
{renderField('Product Number', username, (e) => setUsername(e.target.value), 'text', '#123')}
|
||||
{renderSelectField('Branch Select', selectedBranch, (e) => setSelectedBranch(e.target.value), branches)}
|
||||
</Box>
|
||||
|
||||
{/* السطر الثالث: Expiration Date + Consumption Status */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2,
|
||||
mb: 2,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: { xs: '100%', sm: '45%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: '500', fontSize: '16px', mb: 1 }}
|
||||
>
|
||||
Expiration Date
|
||||
</Typography>
|
||||
<DatePicker
|
||||
value={expirationDate}
|
||||
onChange={(newValue) => setExpirationDate(newValue)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '2px !important',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)',
|
||||
},
|
||||
},
|
||||
'& fieldset': {
|
||||
borderRadius: '20px !important',
|
||||
},
|
||||
flex: 1,
|
||||
minWidth: { xs: '100%', sm: '45%' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</LocalizationProvider>
|
||||
|
||||
<Box sx={{ flex: 1, minWidth: { xs: '100%', sm: '45%' } }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: '500', fontSize: '16px', mb: 1 }}
|
||||
>
|
||||
Consumption Status
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-label="consumption-status"
|
||||
name="row-radio-buttons-group"
|
||||
value={consumptionStatus ? 'yes' : 'no'}
|
||||
onChange={(e) => setConsumptionStatus(e.target.value === 'yes')}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="yes"
|
||||
control={<MuiRadio />}
|
||||
label="Yes"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="no"
|
||||
control={<MuiRadio />}
|
||||
label="No"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* السطر الرابع: Stocks Level + Product Category */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
{renderField('Stocks Level', stocksLevel, (e) => setStocksLevel(e.target.value), 'number', 'Enter level')}
|
||||
{renderSelectField('Product Category', productCategory, (e) => setProductCategory(e.target.value), productCategories)}
|
||||
</Box>
|
||||
|
||||
{/* السطر الخامس: Supplier Name + Supplier Number */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
{renderField('Supplier Name', supplierName, (e) => setSupplierName(e.target.value), 'text', 'Supplier XYZ')}
|
||||
{renderField('Supplier Number', supplierNumber, (e) => setSupplierNumber(e.target.value), 'text', '0900-78601')}
|
||||
</Box>
|
||||
|
||||
{/* السطر السادس: Product Price + Phone Number */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 3 }}>
|
||||
{renderField('Product Price', productPrice, (e) => setProductPrice(e.target.value), 'number', '$200')}
|
||||
{renderField('Product Cost', productCost, (e) => setProductCost(e.target.value), 'number', '$230')}
|
||||
</Box>
|
||||
|
||||
{/* زر الحفظ */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
mb: 2,
|
||||
gap:2
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
sx={{
|
||||
width: '150px',
|
||||
height: '50px',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'white',
|
||||
borderRadius: '10px',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '16px',
|
||||
textTransform: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.dark,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Save Product
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={onShowTable}
|
||||
sx={{
|
||||
width: '150px',
|
||||
height: '50px',
|
||||
borderRadius: '10px',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '16px',
|
||||
textTransform: 'none',
|
||||
color: theme.palette.primary.main,
|
||||
borderColor: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: '#FFECE0',
|
||||
color: theme.palette.primary.main,
|
||||
borderColor: '#FFECE0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Show Table
|
||||
</Button>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductEntry;
|
||||
@@ -0,0 +1,498 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user