1
0

Initial commit - restaurant dashboard

هذا الالتزام موجود في:
RaghadMAlkous
2025-09-04 01:17:15 +03:00
الأصل 13891b47fd
التزام 7b2f8840cb
136 ملفات معدلة مع 16638 إضافات و6033 حذوفات

عرض الملف

@@ -0,0 +1,526 @@
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 Accountant = ({ 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 [Accountants, setAccountants] = useState([]);
const [loading, setLoading] = useState(true);
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
const [AccountantDetails, setAccountantDetails] = useState(null);
// حالات الحذف
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [selectedAccountant, setSelectedAccountant] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
useEffect(() => {
const fetchAccountants = async () => {
setLoading(true);
try {
const response = await authService.getAllAccountants(restaurantId);
if (response.accountants) {
setAccountants(response.accountants);
// تهيئة switchStates حسب قيمة active
const initialStates = {};
response.accountants.forEach(accountants => {
initialStates[accountants.id] = accountants.active === 1; // أو Boolean(waiter.active)
});
setSwitchStates(initialStates);
} else {
setAccountants([]);
}
} catch (error) {
console.error("Failed to fetch waiters:", error);
setAccountants([]);
} finally {
setLoading(false);
}
};
fetchAccountants();
}, [restaurantId]);
const handleSwitchChange = async (id, email) => {
try {
const response = await authService.toggleAccountStatus(email, "Accountant");
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 AccountantData = {
...formData,
restaurant_id: restaurantId,
};
const response = await authService.registerAccountant(AccountantData);
if (response.success) {
alert('Accountant registered successfully!');
setModalOpen(false);
// تحديث الحالة مباشرة بدل إعادة جلب كل البيانات
const newAccountant = response.Accountant || AccountantData;
// تأكد أن الـ API يعيد الـ Accountant المضاف، أو استخدم البيانات المحلية
setAccountants((prev) => [newAccountant, ...prev]);
} else {
if (response.errors) {
const errorsList = Object.entries(response.errors)
.map(([field, msgs]) => {
if (Array.isArray(msgs)) {
return `${field}: ${msgs.join(', ')}`;
} else {
return `${field}: ${msgs}`;
}
})
.join('\n');
alert(`Failed to register Accountant due to validation errors:\n${errorsList}`);
} else {
alert('Failed to register Accountant: ' + 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 filteredAccountants =
shiftFilter === 'all'
? Accountants
: Accountants.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
const displayedAccountants = showAll ? filteredAccountants : filteredAccountants.slice(0, 3);
const handleFilterClick = (event) => {
setFilterAnchorEl(event.currentTarget);
};
const handleFilterClose = () => {
setFilterAnchorEl(null);
};
const handleFilterSelect = (value) => {
setShiftFilter(value);
setFilterAnchorEl(null);
};
// فتح مودال الحذف
const handleDeleteClick = (Accountant) => {
setSelectedAccountant(Accountant);
setConfirmDeleteOpen(true);
};
// تأكيد الحذف
const handleDeleteConfirm = async () => {
if (!selectedAccountant) return;
setIsDeleting(true);
try {
const response = await authService.deleteAccountant(selectedAccountant.id);
// هنا تحقق من وجود message بدل success
if (response.message && response.message.toLowerCase().includes("successfully")) {
// alert(response.message);
setAccountants((prev) => prev.filter((w) => w.id !== selectedAccountant.id));
} else {
alert("Failed to delete Accountant: " + (response.message || "Unknown error"));
}
} catch (error) {
alert("An error occurred: " + error.message);
} finally {
setIsDeleting(false);
setConfirmDeleteOpen(false);
setSelectedAccountant(null);
}
};
const handleOpenDetails = async (id) => {
try {
const response = await authService.getAccountantById(id);
if (response.accountant) {
setAccountantDetails(response.accountant);
setDetailsModalOpen(true);
} else {
alert("Failed to fetch Accountant 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' },
}}
>
Accountant
</Typography>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 1,
justifyContent: { xs: 'center', sm: 'flex-start' },
width: { xs: '100%', sm: 'auto' },
}}
>
{Accountants.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 Accountant
</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>
) : displayedAccountants.length === 0 ? (
<TableRow>
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
No activities found.
</TableCell>
</TableRow>
) : (
displayedAccountants.map((Accountant) => (
<TableRow key={Accountant.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(Accountant.id)}
>
{Accountant.name}
</Box>
</TableCell>
<TableCell>
<Box
sx={{
display: 'flex',
alignItems: 'center',
color: '#4F5867',
fontSize: '14px',
fontWeight: 500,
pl: { xs: 1, sm: 3 },
wordBreak: 'break-word',
}}
>
{Accountant.email}
</Box>
</TableCell>
<TableCell>
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
<Chip {...getShiftChipProps(Accountant.shift_type)} />
</Box>
</TableCell>
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
<IOSSwitch
checked={!!switchStates[Accountant.id]}
onChange={() => handleSwitchChange(Accountant.id, Accountant.email)}
inputProps={{ 'aria-label': 'accountant switch' }}
/>
<DeleteForeverIcon
sx={{ cursor: 'pointer', color: '#e53935' }}
onClick={() => handleDeleteClick(Accountant)}
/>
</Box>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<AddEmployModal
open={modalOpen}
onClose={handleCloseModal}
onConfirm={handleConfirm}
title="Add Accountant"
buttonText="Add Accountant"
/>
<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 "{selectedAccountant?.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={AccountantDetails}
type="Accountant"
/>
</Box>
);
};
export default Accountant;

عرض الملف

@@ -0,0 +1,330 @@
import React, { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Typography,
Box,
Button,
useTheme,
IconButton,
InputAdornment,
MenuItem,
} from '@mui/material';
import VisibilityOutlined from '@mui/icons-material/VisibilityOutlined';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
const AddUserModal = ({
open,
onClose,
onConfirm,
title = 'Add User',
buttonText = 'Add User',
}) => {
const theme = useTheme();
const [formValues, setFormValues] = useState({
name: '',
email: '',
password: '',
password_confirmation: '',
shift_type: '',
working_hours: '',
monthly_salary: '',
});
const [errors, setErrors] = useState({});
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const handleTogglePassword = () => setShowPassword((prev) => !prev);
const handleToggleConfirmPassword = () => setShowConfirmPassword((prev) => !prev);
const handleChange = (key, value) => {
setFormValues((prev) => ({ ...prev, [key]: value }));
setErrors((prev) => ({ ...prev, [key]: null })); // مسح الخطأ عند التعديل
};
const validate = () => {
let tempErrors = {};
if (!formValues.name.trim()) tempErrors.name = 'Name is required';
if (!formValues.email.trim()) tempErrors.email = 'Email is required';
else {
// Regex بسيط للتحقق من صحة الإيميل
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formValues.email))
tempErrors.email = 'Invalid email format';
}
if (!formValues.password) {
tempErrors.password = 'Password is required';
} else if (formValues.password.length < 6) {
tempErrors.password = 'Password must be at least 6 characters long';
}
if (!formValues.password_confirmation)
tempErrors.password_confirmation = 'Confirm password is required';
if (
formValues.password &&
formValues.password_confirmation &&
formValues.password !== formValues.password_confirmation
)
tempErrors.password_confirmation = 'Passwords do not match';
if (!formValues.shift_type) tempErrors.shift_type = 'Shift type is required';
if (!formValues.working_hours) tempErrors.working_hours = 'Working hours is required';
if (!formValues.monthly_salary) tempErrors.monthly_salary = 'Monthly salary is required';
setErrors(tempErrors);
return Object.keys(tempErrors).length === 0;
};
const handleSubmit = () => {
if (validate()) {
onConfirm(formValues);
// إعادة تعيين الحقول بعد النجاح
setFormValues({
name: '',
email: '',
password: '',
password_confirmation: '',
shift_type: '',
working_hours: '',
monthly_salary: '',
});
setErrors({}); // تصفير الأخطاء أيضًا
}
};
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle sx={{ fontWeight: 600, fontSize: '20px' }}>{title}</DialogTitle>
<DialogContent
dividers
sx={{
overflow: 'auto', // يسمح بالتمرير
'&::-webkit-scrollbar': {
display: 'none', // يخفي الـ scrollbar على Webkit (Chrome, Edge, Safari)
},
scrollbarWidth: 'none', // يخفي scrollbar على Firefox
msOverflowStyle: 'none', // يخفي scrollbar على IE 10+
}}
>
{/* Name */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Name
</Typography>
<TextField
placeholder="John Doe"
type="text"
variant="outlined"
fullWidth
value={formValues.name}
onChange={(e) => handleChange('name', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.name}
helperText={errors.name}
/>
</Box>
{/* Email */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Email
</Typography>
<TextField
placeholder="example@example.com"
type="email"
variant="outlined"
fullWidth
value={formValues.email}
onChange={(e) => handleChange('email', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.email}
helperText={errors.email}
/>
</Box>
{/* Password */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Password
</Typography>
<TextField
type={showPassword ? 'text' : 'password'}
placeholder="Enter password"
fullWidth
variant="outlined"
autoComplete="new-password"
value={formValues.password}
onChange={(e) => handleChange('password', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.password}
helperText={errors.password}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleTogglePassword} edge="end">
{showPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
),
}}
/>
</Box>
{/* Confirm Password */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Confirm Password
</Typography>
<TextField
type={showConfirmPassword ? 'text' : 'password'}
placeholder="Confirm password"
fullWidth
variant="outlined"
autoComplete="new-password"
value={formValues.password_confirmation}
onChange={(e) => handleChange('password_confirmation', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.password_confirmation}
helperText={errors.password_confirmation}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleToggleConfirmPassword} edge="end">
{showConfirmPassword ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlined />}
</IconButton>
</InputAdornment>
),
}}
/>
</Box>
{/* Shift Type */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Shift Type
</Typography>
<TextField
select
fullWidth
variant="outlined"
value={formValues.shift_type}
onChange={(e) => handleChange('shift_type', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.shift_type}
helperText={errors.shift_type}
placeholder="Select shift type"
>
<MenuItem value="morning">Morning</MenuItem>
<MenuItem value="evening">Evening</MenuItem>
</TextField>
</Box>
{/* Working Hours */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Working Hours
</Typography>
<TextField
placeholder="12"
type="number"
variant="outlined"
fullWidth
value={formValues.working_hours}
onChange={(e) => handleChange('working_hours', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.working_hours}
helperText={errors.working_hours}
/>
</Box>
{/* Monthly Salary */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
Monthly Salary
</Typography>
<TextField
placeholder="500"
type="number"
variant="outlined"
fullWidth
value={formValues.monthly_salary}
onChange={(e) => handleChange('monthly_salary', e.target.value)}
sx={fieldStyle(theme)}
error={!!errors.monthly_salary}
helperText={errors.monthly_salary}
/>
</Box>
</DialogContent>
<DialogActions sx={{ pr: 3, pb: 2 }}>
<Button
onClick={onClose}
sx={{
borderRadius: '8px',
fontWeight: 600,
fontSize: '14px',
height: '40px',
width: '135px',
textTransform: 'none',
}}
variant="outlined"
size="small"
>
Cancel
</Button>
<Button
onClick={handleSubmit}
sx={{
color: 'white',
borderRadius: '8px',
fontWeight: 600,
fontSize: '14px',
height: '40px',
width: '135px',
textTransform: 'none',
}}
variant="contained"
size="small"
>
{buttonText}
</Button>
</DialogActions>
</Dialog>
);
};
const fieldStyle = (theme) => ({
'& input': { fontWeight: 500, fontSize: '15px' },
'& label': { fontWeight: 600, fontSize: '15px', color: 'black' },
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.grey[400],
},
'& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.primary.main,
},
'& input::-ms-reveal, & input::-ms-clear': {
display: 'none',
},
});
export default AddUserModal;

عرض الملف

@@ -0,0 +1,158 @@
import React from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
Box,
Typography,
useTheme,
Divider
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
const ConfirmationDialog = ({ open, onClose, onConfirm }) => {
const theme = useTheme();
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
width: '525px',
height: '300px',
maxWidth: '100%',
maxHeight: '100%',
borderRadius: '12px',
overflow: 'hidden'
}
}}
>
<Box sx={{
p: 3,
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
}}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
mb: 3,
ml: -5,
}}
>
{/* الدوائر */}
<Box sx={{
position: 'relative',
width: 100,
height: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
{/* الدائرة الخلفية */}
<Box sx={{
position: 'absolute',
backgroundColor: '#F5F8FF',
width: '100%',
height: '100%',
borderRadius: '50%',
zIndex: 0,
}} />
{/* الدائرة الأمامية مع الأيقونة */}
<Box sx={{
width: 80,
height: 80,
borderRadius: '50%',
backgroundColor: '#E9EFFF',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1,
}}>
<img
src='/images/createProfile/Successful.png'
style={{
width: 40,
height: 40,
objectFit: 'contain',
}}
alt="Success icon"
/>
</Box>
</Box>
{/* العنوان بجانب الدائرة */}
<DialogTitle sx={{ p: 0 }}>
<Typography
variant="h4"
component="div"
sx={{
fontWeight: 600,
fontSize: '28px',
lineHeight: '1.2'
}}
>
Register successful!
</Typography>
</DialogTitle>
</Box>
<DialogContent sx={{ p: 0 }}>
<DialogContentText sx={{
textAlign: 'center',
mb: 0,
fontSize: '16px',
fontWeight: 500,
color: theme.palette.text.secondary
}}>
Congratulations! you have registered your restaurant successfully on our platform
</DialogContentText>
</DialogContent>
<DialogActions
sx={{
p: 0,
px: 3,
pt: 2,
flexDirection: 'column',
gap: 2,
}}
>
{/* الزر الأول - Filled */}
<Button
variant="contained"
fullWidth
onClick={onClose}
sx={{
fontWeight: 600,
fontSize: '16px',
height: '48px',
borderRadius: '50px',
textTransform: 'none',
color: 'white',
backgroundColor: theme.palette.primary.main,
'&:hover': {
backgroundColor: theme.palette.primary.dark,
},
}}
>
Done
</Button>
</DialogActions>
</Box>
</Dialog>
);
};
export default ConfirmationDialog;

عرض الملف

@@ -0,0 +1,66 @@
import React from "react";
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Typography,
Button,
Box,
} from "@mui/material";
const EmployeeDetailsModal = ({ open, onClose, employee, type }) => {
if (!employee) return null;
// دالة لتحويل الحالة الرقمية إلى نصية
const getStatusText = (active) => (active ? "Active" : "Inactive");
// عنوان المودال حسب نوع الموظف
const getTitle = () => {
switch (type?.toLowerCase()) {
case "waiter":
return "Waiter Details";
case "cooker":
return "Cooker Details";
case "accountant":
return "Accountant Details";
default:
return "Employee Details";
}
};
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>{getTitle()}</DialogTitle>
<DialogContent dividers>
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{employee.name && <Typography><b>Name:</b> {employee.name}</Typography>}
{employee.email && <Typography><b>Email:</b> {employee.email}</Typography>}
{employee.shift_type && <Typography><b>Shift:</b> {employee.shift_type}</Typography>}
{employee.working_hours !== undefined && (
<Typography><b>Working Hours:</b> {employee.working_hours}</Typography>
)}
{employee.monthly_salary !== undefined && (
<Typography><b>Monthly Salary:</b> {employee.monthly_salary}</Typography>
)}
{employee.active !== undefined && (
<Typography><b>Status:</b> {getStatusText(employee.active)}</Typography>
)}
{employee.code && <Typography><b>Code:</b> {employee.code}</Typography>}
{employee.expires_at && <Typography><b>Expires At:</b> {employee.expires_at}</Typography>}
</Box>
</DialogContent>
<DialogActions>
<Button
onClick={onClose}
variant="outlined"
sx={{ textTransform: "none", minWidth: { md: 120 } }}
>
Close
</Button>
</DialogActions>
</Dialog>
);
};
export default EmployeeDetailsModal;

عرض الملف

@@ -0,0 +1,57 @@
import { styled } from '@mui/material/styles';
import Switch from '@mui/material/Switch';
const IOSSwitch = styled((props) => (
<Switch focusVisibleClassName=".Mui-focusVisible" disableRipple {...props} />
))(({ theme }) => ({
width: 42,
height: 26,
padding: 0,
'& .MuiSwitch-switchBase': {
padding: 0,
margin: 2,
transitionDuration: '300ms',
'&.Mui-checked': {
transform: 'translateX(16px)',
color: '#fff',
'& + .MuiSwitch-track': {
backgroundColor: theme.palette.primary.main,
opacity: 1,
border: 0,
},
'&.Mui-disabled + .MuiSwitch-track': {
opacity: 0.5,
},
},
'&.Mui-focusVisible .MuiSwitch-thumb': {
color: theme.palette.primary.main,
border: '6px solid #fff',
},
'&.Mui-disabled .MuiSwitch-thumb': {
color:
theme.palette.mode === 'light'
? theme.palette.grey[100]
: theme.palette.grey[600],
},
'&.Mui-disabled + .MuiSwitch-track': {
opacity: theme.palette.mode === 'light' ? 0.7 : 0.3,
},
},
'& .MuiSwitch-thumb': {
boxSizing: 'border-box',
width: 22,
height: 22,
borderRadius: 11,
},
'& .MuiSwitch-track': {
borderRadius: 26 / 2,
backgroundColor:
theme.palette.mode === 'light' ? '#E9E9EA' : 'rgba(255,255,255,0.35)',
opacity: 1,
transition: theme.transitions.create(['background-color'], {
duration: 500,
}),
},
}));
export default IOSSwitch;

عرض الملف

@@ -0,0 +1,526 @@
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 Cooker = ({ 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 [Cookers, setCookers] = useState([]);
const [loading, setLoading] = useState(true);
const [detailsModalOpen, setDetailsModalOpen] = useState(false);
const [CookerDetails, setCookerDetails] = useState(null);
// حالات الحذف
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [selectedCooker, setSelectedCooker] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
// استدعاء API
useEffect(() => {
const fetchCookers = async () => {
setLoading(true);
try {
const response = await authService.getAllCookers(restaurantId);
if (response.cookers) {
setCookers(response.cookers);
const initialStates = {};
response.cookers.forEach(cookers => {
initialStates[cookers.id] = cookers.active === 1; // أو Boolean(waiter.active)
});
setSwitchStates(initialStates);
} else {
setCookers([]);
}
} catch (error) {
console.error("Failed to fetch Cookers:", error);
setCookers([]);
} finally {
setLoading(false);
}
};
fetchCookers();
}, [restaurantId]);
const handleSwitchChange = async (id, email) => {
try {
const response = await authService.toggleAccountStatus(email, "Cooker");
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 CookerData = {
...formData,
restaurant_id: restaurantId,
};
const response = await authService.registerCooker(CookerData);
if (response.success) {
alert('Cooker registered successfully!');
setModalOpen(false);
// تحديث الحالة مباشرة بدل إعادة جلب كل البيانات
const newCooker = response.Cooker || CookerData;
// تأكد أن الـ API يعيد الـ Cooker المضاف، أو استخدم البيانات المحلية
setCookers((prev) => [newCooker, ...prev]);
} else {
if (response.errors) {
const errorsList = Object.entries(response.errors)
.map(([field, msgs]) => {
if (Array.isArray(msgs)) {
return `${field}: ${msgs.join(', ')}`;
} else {
return `${field}: ${msgs}`;
}
})
.join('\n');
alert(`Failed to register Cooker due to validation errors:\n${errorsList}`);
} else {
alert('Failed to register Cooker: ' + 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 filteredCookers =
shiftFilter === 'all'
? Cookers
: Cookers.filter((w) => w.shift_type.toLowerCase() === shiftFilter);
const displayedCookers = showAll ? filteredCookers : filteredCookers.slice(0, 3);
const handleFilterClick = (event) => {
setFilterAnchorEl(event.currentTarget);
};
const handleFilterClose = () => {
setFilterAnchorEl(null);
};
const handleFilterSelect = (value) => {
setShiftFilter(value);
setFilterAnchorEl(null);
};
// فتح مودال الحذف
const handleDeleteClick = (Cooker) => {
setSelectedCooker(Cooker);
setConfirmDeleteOpen(true);
};
// تأكيد الحذف
const handleDeleteConfirm = async () => {
if (!selectedCooker) return;
setIsDeleting(true);
try {
const response = await authService.deleteCooker(selectedCooker.id);
// هنا تحقق من وجود message بدل success
if (response.message && response.message.toLowerCase().includes("successfully")) {
// alert(response.message);
setCookers((prev) => prev.filter((w) => w.id !== selectedCooker.id));
} else {
alert("Failed to delete Cooker: " + (response.message || "Unknown error"));
}
} catch (error) {
alert("An error occurred: " + error.message);
} finally {
setIsDeleting(false);
setConfirmDeleteOpen(false);
setSelectedCooker(null);
}
};
const handleOpenDetails = async (id) => {
try {
const response = await authService.getCookerById(id);
if (response.cooker) {
setCookerDetails(response.cooker);
setDetailsModalOpen(true);
} else {
alert("Failed to fetch Cooker 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' },
}}
>
Cooker
</Typography>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 1,
justifyContent: { xs: 'center', sm: 'flex-start' },
width: { xs: '100%', sm: 'auto' },
}}
>
{Cookers.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 Cooker
</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>
) : displayedCookers.length === 0 ? (
<TableRow>
<TableCell colSpan={4} align="center" sx={{ color: '#999', fontStyle: 'italic' }}>
No activities found.
</TableCell>
</TableRow>
) : (
displayedCookers.map((Cooker) => (
<TableRow key={Cooker.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(Cooker.id)}
>
{Cooker.name}
</Box>
</TableCell>
<TableCell>
<Box
sx={{
display: 'flex',
alignItems: 'center',
color: '#4F5867',
fontSize: '14px',
fontWeight: 500,
pl: { xs: 1, sm: 3 },
wordBreak: 'break-word',
}}
>
{Cooker.email}
</Box>
</TableCell>
<TableCell>
<Box sx={{ pl: { xs: 1, sm: 8 } }}>
<Chip {...getShiftChipProps(Cooker.shift_type)} />
</Box>
</TableCell>
<TableCell sx={{ width: '28%', pl: { xs: 1, sm: 8 } }}>
<Box sx={{ display: 'flex', gap: 5, alignItems: 'center' }}>
<IOSSwitch
checked={!!switchStates[Cooker.id]}
onChange={() => handleSwitchChange(Cooker.id, Cooker.email)}
inputProps={{ 'aria-label': 'accountant switch' }}
/>
<DeleteForeverIcon
sx={{ cursor: 'pointer', color: '#e53935' }}
onClick={() => handleDeleteClick(Cooker)}
/>
</Box>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<AddEmployModal
open={modalOpen}
onClose={handleCloseModal}
onConfirm={handleConfirm}
title="Add Cooker"
buttonText="Add Cooker"
/>
<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 "{selectedCooker?.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={CookerDetails}
type="Cooker"
/>
</Box>
);
};
export default Cooker;

عرض الملف

@@ -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;