نسخ من RaghadAlkhous/RestaurantDash
Initial commit - restaurant dashboard
هذا الالتزام موجود في:
526
src/components/Home/Employ/contcet/Accountant.js
Normal file
526
src/components/Home/Employ/contcet/Accountant.js
Normal file
@@ -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;
|
||||
330
src/components/Home/Employ/contcet/AddEmployModal.js
Normal file
330
src/components/Home/Employ/contcet/AddEmployModal.js
Normal file
@@ -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;
|
||||
158
src/components/Home/Employ/contcet/ConfirmationDialog.js
Normal file
158
src/components/Home/Employ/contcet/ConfirmationDialog.js
Normal file
@@ -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;
|
||||
66
src/components/Home/Employ/contcet/EmployeeDetailsModal.js
Normal file
66
src/components/Home/Employ/contcet/EmployeeDetailsModal.js
Normal file
@@ -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;
|
||||
57
src/components/Home/Employ/contcet/IOSSwitch.js
Normal file
57
src/components/Home/Employ/contcet/IOSSwitch.js
Normal file
@@ -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;
|
||||
526
src/components/Home/Employ/contcet/cooker.js
Normal file
526
src/components/Home/Employ/contcet/cooker.js
Normal file
@@ -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;
|
||||
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;
|
||||
المرجع في مشكلة جديدة
حظر مستخدم