نسخ من RaghadAlkhous/RestaurantDash
Initial commit - restaurant dashboard
هذا الالتزام موجود في:
519
src/components/Home/Employ/contcet/waiter.js
Normal file
519
src/components/Home/Employ/contcet/waiter.js
Normal file
@@ -0,0 +1,519 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Button,
|
||||
Chip,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import AddEmployModal from './AddEmployModal';
|
||||
import authService from '../../../../services/authService';
|
||||
import IOSSwitch from './IOSSwitch';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import EmployeeDetailsModal from './EmployeeDetailsModal';
|
||||
|
||||
const Waiter = ({ restaurantId }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [switchStates, setSwitchStates] = useState({});
|
||||
const [shiftFilter, setShiftFilter] = useState('all');
|
||||
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
||||
const [waiters, setWaiters] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
|
||||
const [waiterDetails, setWaiterDetails] = useState(null);
|
||||
|
||||
|
||||
// حالات الحذف
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [selectedWaiter, setSelectedWaiter] = useState(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// استدعاء API
|
||||
useEffect(() => {
|
||||
const fetchWaiters = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await authService.getAllWaiters(restaurantId);
|
||||
if (response.waiters) {
|
||||
setWaiters(response.waiters);
|
||||
|
||||
// تهيئة switchStates حسب قيمة active
|
||||
const initialStates = {};
|
||||
response.waiters.forEach(waiter => {
|
||||
initialStates[waiter.id] = waiter.active === 1; // أو Boolean(waiter.active)
|
||||
});
|
||||
setSwitchStates(initialStates);
|
||||
} else {
|
||||
setWaiters([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch waiters:", error);
|
||||
setWaiters([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchWaiters();
|
||||
}, [restaurantId]);
|
||||
|
||||
|
||||
const handleSwitchChange = async (id, email) => {
|
||||
try {
|
||||
const response = await authService.toggleAccountStatus(email, "Waiter");
|
||||
|
||||
if (response.success) {
|
||||
setSwitchStates((prev) => ({
|
||||
...prev,
|
||||
[id]: response.data,
|
||||
}));
|
||||
// alert(response.message);
|
||||
} else {
|
||||
alert("Failed: " + response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleOpenModal = () => setModalOpen(true);
|
||||
const handleCloseModal = () => setModalOpen(false);
|
||||
|
||||
const handleConfirm = async (formData) => {
|
||||
try {
|
||||
const waiterData = {
|
||||
...formData,
|
||||
restaurant_id: restaurantId,
|
||||
};
|
||||
|
||||
const response = await authService.registerWaiter(waiterData);
|
||||
|
||||
if (response.success) {
|
||||
alert('Waiter registered successfully!');
|
||||
setModalOpen(false);
|
||||
// إعادة التحميل بعد الإضافة
|
||||
const updated = await authService.getAllWaiters(restaurantId);
|
||||
if (updated.waiters) setWaiters(updated.waiters);
|
||||
} else {
|
||||
if (response.errors) {
|
||||
const errorsList = Object.entries(response.errors)
|
||||
.map(([field, msgs]) => `${field}: ${msgs.join(', ')}`)
|
||||
.join('\n');
|
||||
alert(`Failed to register waiter due to validation errors:\n${errorsList}`);
|
||||
} else {
|
||||
alert('Failed to register waiter: ' + response.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('An error occurred: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const getShiftChipProps = (shiftType) => {
|
||||
switch (shiftType?.toLowerCase()) {
|
||||
case 'morning':
|
||||
return {
|
||||
label: 'Morning',
|
||||
sx: {
|
||||
backgroundColor: '#E7F4EE',
|
||||
color: '#0D894F',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
case 'evening':
|
||||
return {
|
||||
label: 'Evening',
|
||||
sx: {
|
||||
backgroundColor: '#FDF1E8',
|
||||
color: '#E46A11',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: shiftType,
|
||||
sx: {
|
||||
backgroundColor: '#E0E0E0',
|
||||
color: '#424242',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
minWidth: 80,
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const filteredWaiters =
|
||||
shiftFilter === 'all'
|
||||
? waiters
|
||||
: waiters.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
|
||||
|
||||
const displayedWaiters = showAll ? filteredWaiters : filteredWaiters.slice(0, 3);
|
||||
|
||||
const handleFilterClick = (event) => {
|
||||
setFilterAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleFilterClose = () => {
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleFilterSelect = (value) => {
|
||||
setShiftFilter(value);
|
||||
setFilterAnchorEl(null);
|
||||
};
|
||||
|
||||
// فتح مودال الحذف
|
||||
const handleDeleteClick = (waiter) => {
|
||||
setSelectedWaiter(waiter);
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
// تأكيد الحذف
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (!selectedWaiter) return;
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const response = await authService.deleteWaiter(selectedWaiter.id);
|
||||
|
||||
// هنا تحقق من وجود message بدل success
|
||||
if (response.message && response.message.toLowerCase().includes("successfully")) {
|
||||
// alert(response.message);
|
||||
setWaiters((prev) => prev.filter((w) => w.id !== selectedWaiter.id));
|
||||
} else {
|
||||
alert("Failed to delete waiter: " + (response.message || "Unknown error"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("An error occurred: " + error.message);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
setSelectedWaiter(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenDetails = async (id) => {
|
||||
try {
|
||||
const response = await authService.getWaiterById(id);
|
||||
if (response.waiter) {
|
||||
setWaiterDetails(response.waiter);
|
||||
setDetailsModalOpen(true);
|
||||
} else {
|
||||
alert("Failed to fetch waiter details");
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '93.5%' },
|
||||
p: { xs: 2, sm: 3 },
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'stretch', sm: 'center' },
|
||||
mb: 2,
|
||||
gap: { xs: 1, sm: 0 },
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '18px', sm: '20px' },
|
||||
mb: { xs: 1, sm: 0 },
|
||||
textAlign: { xs: 'center', sm: 'left' },
|
||||
}}
|
||||
>
|
||||
Waiter
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
justifyContent: { xs: 'center', sm: 'flex-start' },
|
||||
width: { xs: '100%', sm: 'auto' },
|
||||
}}
|
||||
>
|
||||
{waiters.length > 3 && (
|
||||
<Button
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
sx={{
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
textTransform: 'none',
|
||||
minWidth: 90,
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
{showAll ? 'See Less' : 'See All'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
color: '#667085',
|
||||
borderColor: '#e0e0e0',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '40px',
|
||||
width: { xs: '120px', sm: '120px', md: '99px' },
|
||||
fontSize: { xs: '0', sm: '13px', md: '14px' },
|
||||
fontWeight: 600,
|
||||
p: 0,
|
||||
m: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'unset',
|
||||
}}
|
||||
startIcon={<TuneIcon fontSize={isMobile ? 'small' : 'medium'} />}
|
||||
onClick={handleFilterClick}
|
||||
>
|
||||
{isMobile ? '' : 'Filters'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleOpenModal}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
height: '40px',
|
||||
width: { xs: '100%', sm: '135px' },
|
||||
textTransform: 'none',
|
||||
minWidth: { xs: 'unset', sm: '135px' },
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Add Waiter
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
boxShadow: 'none',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 2,
|
||||
minWidth: 320,
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
sx={{
|
||||
minWidth: { sm: 550, md: 650 },
|
||||
tableLayout: 'auto',
|
||||
}}
|
||||
aria-label="recent activity table"
|
||||
size={isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5', color: '#61677F' }}>
|
||||
<TableRow sx={{ '& th': { borderBottom: 'none' } }}>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Email
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 10 } }}>
|
||||
Shift Type
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 500, fontSize: '16px', color: '#61677F', width: '25%', pl: { xs: 1, sm: 8 } }}>
|
||||
Actions
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
Loading...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : displayedWaiters.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
|
||||
No activities found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
displayedWaiters.map((waiter) => (
|
||||
<TableRow key={waiter.id}>
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "#296adaff",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 6 },
|
||||
wordBreak: "break-word",
|
||||
cursor: "pointer",
|
||||
"&:hover": { color: "#71a5ffff" },
|
||||
}}
|
||||
onClick={() => handleOpenDetails(waiter.id)}
|
||||
>
|
||||
{waiter.name}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: '#4F5867',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
pl: { xs: 1, sm: 3 },
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{waiter.email}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
|
||||
<Chip {...getShiftChipProps(waiter.shift_type)} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
|
||||
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
|
||||
<IOSSwitch
|
||||
checked={!!switchStates[waiter.id]}
|
||||
onChange={() => handleSwitchChange(waiter.id, waiter.email)}
|
||||
inputProps={{ 'aria-label': 'accountant switch' }}
|
||||
/>
|
||||
|
||||
<DeleteForeverIcon
|
||||
sx={{ cursor: 'pointer', color: '#e53935' }}
|
||||
onClick={() => handleDeleteClick(waiter)}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<AddEmployModal
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onConfirm={handleConfirm}
|
||||
title="Add Waiter"
|
||||
buttonText="Add Waiter"
|
||||
/>
|
||||
|
||||
<Menu
|
||||
anchorEl={filterAnchorEl}
|
||||
open={Boolean(filterAnchorEl)}
|
||||
onClose={handleFilterClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: 'white',
|
||||
px: 1,
|
||||
position: 'relative',
|
||||
width: '120px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['all', 'morning', 'evening'].map((shift) => (
|
||||
<MenuItem
|
||||
key={shift}
|
||||
selected={shiftFilter === shift}
|
||||
onClick={() => handleFilterSelect(shift)}
|
||||
sx={{
|
||||
color: shiftFilter === shift ? '#FF914D' : '#4F5867',
|
||||
backgroundColor: shiftFilter === shift ? '#fffcf9d5' : 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f0f0ff',
|
||||
transition: 'background-color 150ms ease-in-out',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{shift.charAt(0).toUpperCase() + shift.slice(1)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
{/* مودال تأكيد الحذف */}
|
||||
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete "{selectedWaiter?.name}"? This action cannot be undone.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleDeleteConfirm}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<EmployeeDetailsModal
|
||||
open={detailsModalOpen}
|
||||
onClose={() => setDetailsModalOpen(false)}
|
||||
employee={waiterDetails} // بدل waiter
|
||||
type="waiter"
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Waiter;
|
||||
المرجع في مشكلة جديدة
حظر مستخدم