457 lines
14 KiB
JavaScript
457 lines
14 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
|
import {
|
|
useMediaQuery,
|
|
Box,
|
|
Typography,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Paper,
|
|
IconButton,
|
|
Skeleton,
|
|
Button,
|
|
TextField,
|
|
MenuItem,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Menu,
|
|
Chip,
|
|
} from "@mui/material";
|
|
import { useTheme } from "@mui/material/styles";
|
|
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
|
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
|
import AddIcon from "@mui/icons-material/Add";
|
|
import SaveIcon from "@mui/icons-material/Save";
|
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
|
import TuneIcon from "@mui/icons-material/Tune";
|
|
import authService from "../../../services/authService";
|
|
|
|
const SimplePagination = ({ currentPage, pageCount, onChange }) => {
|
|
const theme = useTheme();
|
|
const handlePrev = () => currentPage > 1 && onChange(currentPage - 1);
|
|
const handleNext = () => currentPage < pageCount && onChange(currentPage + 1);
|
|
|
|
return (
|
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
|
<IconButton
|
|
size="small"
|
|
onClick={handlePrev}
|
|
disabled={currentPage <= 1}
|
|
sx={{
|
|
borderRadius: "8px",
|
|
backgroundColor: "#FFECE0",
|
|
// "&:hover": { backgroundColor: "#FFD6B5" },
|
|
color: theme.palette.primary.main,
|
|
// "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
|
|
}}
|
|
>
|
|
<ArrowBackIosNewIcon fontSize="small" />
|
|
</IconButton>
|
|
|
|
<Box
|
|
sx={{
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: "8px",
|
|
backgroundColor: theme.palette.primary.main,
|
|
color: "#fff",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
fontWeight: 600,
|
|
fontSize: 14,
|
|
userSelect: "none",
|
|
}}
|
|
>
|
|
{currentPage}
|
|
</Box>
|
|
|
|
<IconButton
|
|
size="small"
|
|
onClick={handleNext}
|
|
disabled={currentPage >= pageCount}
|
|
sx={{
|
|
borderRadius: "8px",
|
|
backgroundColor: "#FFECE0",
|
|
// "&:hover": { backgroundColor: "#FFD6B5" },
|
|
color: theme.palette.primary.main,
|
|
// "&.Mui-disabled": { color: "#ccc", backgroundColor: "#FFF5E6" },
|
|
}}
|
|
>
|
|
<ArrowForwardIosIcon fontSize="small" />
|
|
</IconButton>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const statusOptions = ["available", "unavailable"];
|
|
|
|
const TablesManager = ({ restaurantId }) => {
|
|
const theme = useTheme();
|
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
|
const itemsPerPage = 4;
|
|
|
|
const [rows, setRows] = useState([]);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [addingRow, setAddingRow] = useState(false);
|
|
const [newRow, setNewRow] = useState({
|
|
restaurant_id: restaurantId,
|
|
table_number: "",
|
|
capacity: "",
|
|
status: "available",
|
|
});
|
|
|
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
|
const [rowToDelete, setRowToDelete] = useState(null);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
// فلتر
|
|
const [filterAnchorEl, setFilterAnchorEl] = useState(null);
|
|
const [statusFilter, setStatusFilter] = useState("all");
|
|
|
|
const fetchTables = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const result = await authService.getTablesByRestaurant(restaurantId);
|
|
if (result.success) setRows(result.data);
|
|
} catch (err) {
|
|
console.error("❌ Error fetching tables:", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (restaurantId) fetchTables();
|
|
}, [restaurantId]);
|
|
|
|
const handleSaveRow = async () => {
|
|
if (!newRow.table_number || !newRow.capacity) return;
|
|
try {
|
|
const result = await authService.addTable(newRow);
|
|
if (result.success) {
|
|
setRows((prev) => [result.data, ...prev]);
|
|
setAddingRow(false);
|
|
setNewRow({
|
|
restaurant_id: restaurantId,
|
|
table_number: "",
|
|
capacity: "",
|
|
status: "available",
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error("❌ Error adding table:", err);
|
|
}
|
|
};
|
|
|
|
const handleDeleteRow = (row) => {
|
|
setRowToDelete(row);
|
|
setConfirmDeleteOpen(true);
|
|
};
|
|
|
|
const handleConfirmDelete = async () => {
|
|
if (!rowToDelete) return;
|
|
setIsSaving(true);
|
|
try {
|
|
const result = await authService.deleteTable(rowToDelete.id);
|
|
if (result.success) setRows((prev) => prev.filter((r) => r.id !== rowToDelete.id));
|
|
} catch (err) {
|
|
console.error(err);
|
|
} finally {
|
|
setIsSaving(false);
|
|
setConfirmDeleteOpen(false);
|
|
}
|
|
};
|
|
|
|
const handleFilterClick = (event) => setFilterAnchorEl(event.currentTarget);
|
|
const handleFilterClose = () => setFilterAnchorEl(null);
|
|
const handleFilterSelect = (value) => {
|
|
setStatusFilter(value);
|
|
setFilterAnchorEl(null);
|
|
};
|
|
|
|
const filteredRows =
|
|
(statusFilter === "all" ? rows : rows.filter((r) => r.status === statusFilter))
|
|
.sort((a, b) => parseInt(a.table_number) - parseInt(b.table_number));
|
|
|
|
const pageCount = Math.ceil(filteredRows.length / itemsPerPage);
|
|
const paginatedRows = filteredRows.slice(
|
|
(currentPage - 1) * itemsPerPage,
|
|
currentPage * itemsPerPage
|
|
);
|
|
|
|
const getStatusChipProps = (status) => {
|
|
switch (status?.toLowerCase()) {
|
|
case "available":
|
|
return {
|
|
label: "Available",
|
|
sx: {
|
|
backgroundColor: "#E7F4EE",
|
|
color: "#0D894F",
|
|
fontWeight: 600,
|
|
fontSize: "13px",
|
|
minWidth: 90,
|
|
},
|
|
};
|
|
case "unavailable":
|
|
return {
|
|
label: "Unavailable",
|
|
sx: {
|
|
backgroundColor: "#fde8e8ff",
|
|
color: "#e41111ff",
|
|
fontWeight: 600,
|
|
fontSize: "13px",
|
|
minWidth: 100,
|
|
},
|
|
};
|
|
default:
|
|
return {
|
|
label: status,
|
|
sx: {
|
|
backgroundColor: "#FDF1E8",
|
|
color: "#E46A11",
|
|
fontWeight: 600,
|
|
fontSize: "13px",
|
|
minWidth: 80,
|
|
},
|
|
};
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
width: { xs: "100%", sm: "95.5%" },
|
|
pt: { xs: 2, sm: 2 },
|
|
pl: { xs: 2, sm: 3 },
|
|
pr: { xs: 2, sm: 3 },
|
|
pb: { xs: 2, sm: 2 },
|
|
// p: { xs: 1.5, sm: 2 },
|
|
// mb: { xs: 2, sm: 2 },
|
|
backgroundColor: "white",
|
|
borderRadius: 2,
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<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" },
|
|
textAlign: { xs: "center", sm: "left" },
|
|
}}
|
|
>
|
|
Tables
|
|
</Typography>
|
|
|
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
|
{/* فلتر */}
|
|
<Button
|
|
variant="outlined"
|
|
sx={{
|
|
textTransform: "none",
|
|
color: "#667085",
|
|
borderColor: "#e0e0e0",
|
|
borderRadius: "8px",
|
|
height: "40px",
|
|
width: { xs: "120px", sm: "120px", md: "99px" },
|
|
fontSize: { xs: "0", sm: "13px", md: "14px" },
|
|
fontWeight: 600,
|
|
minWidth: "unset",
|
|
}}
|
|
startIcon={<TuneIcon fontSize={isMobile ? "small" : "small"} />}
|
|
onClick={handleFilterClick}
|
|
>
|
|
{isMobile ? "" : "Filters"}
|
|
</Button>
|
|
|
|
<Button
|
|
variant={addingRow ? "outlined" : "contained"}
|
|
color="primary"
|
|
startIcon={<AddIcon />}
|
|
onClick={() => setAddingRow(!addingRow)}
|
|
sx={{
|
|
color: addingRow ? "primary.main" : "white",
|
|
borderRadius: "8px",
|
|
fontWeight: 600,
|
|
fontSize: "14px",
|
|
height: "40px",
|
|
width: { xs: "100%", sm: "135px" },
|
|
textTransform: "none",
|
|
}}
|
|
>
|
|
{addingRow ? "Cancel" : "Add"}
|
|
</Button>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Menu الفلاتر */}
|
|
<Menu anchorEl={filterAnchorEl} open={Boolean(filterAnchorEl)} onClose={handleFilterClose}>
|
|
{["all", "available", "unavailable"].map((status) => (
|
|
<MenuItem
|
|
key={status}
|
|
selected={statusFilter === status}
|
|
onClick={() => handleFilterSelect(status)}
|
|
sx={{
|
|
color: statusFilter === status ? "#FF914D" : "#4F5867",
|
|
fontWeight: statusFilter === status ? 700 : 500,
|
|
}}
|
|
>
|
|
{status.charAt(0).toUpperCase() + status.slice(1)}
|
|
</MenuItem>
|
|
))}
|
|
</Menu>
|
|
|
|
{/* جدول */}
|
|
<TableContainer
|
|
component={Paper}
|
|
sx={{
|
|
boxShadow: "none",
|
|
border: "1px solid #e0e0e0",
|
|
borderRadius: 2,
|
|
minWidth: 320,
|
|
height: {xs:"310px",md:"349px"},
|
|
}}
|
|
>
|
|
<Table
|
|
sx={{ minWidth: { sm: 550, md: 650 }, tableLayout: "auto" }}
|
|
aria-label="tables"
|
|
size={isMobile ? "small" : "medium"}
|
|
>
|
|
<TableHead sx={{ backgroundColor: "#f5f5f5" }}>
|
|
<TableRow>
|
|
<TableCell sx={{ pl: { md: 7 } }}>Table Number</TableCell>
|
|
<TableCell>Capacity</TableCell>
|
|
<TableCell>Status</TableCell>
|
|
<TableCell>Actions</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{loading
|
|
? Array.from({ length: itemsPerPage }).map((_, idx) => (
|
|
<TableRow key={`skeleton-${idx}`}>
|
|
<TableCell ><Skeleton variant="text" /></TableCell>
|
|
<TableCell><Skeleton variant="text" /></TableCell>
|
|
<TableCell><Skeleton variant="text" /></TableCell>
|
|
<TableCell><Skeleton variant="text" /></TableCell>
|
|
</TableRow>
|
|
))
|
|
: (
|
|
<>
|
|
{addingRow && (
|
|
<TableRow>
|
|
<TableCell >
|
|
<TextField
|
|
size="small"
|
|
placeholder="Table Number"
|
|
value={newRow.table_number}
|
|
onChange={(e) => setNewRow({ ...newRow, table_number: e.target.value })}
|
|
/>
|
|
</TableCell>
|
|
<TableCell>
|
|
<TextField
|
|
size="small"
|
|
type="number"
|
|
placeholder="Capacity"
|
|
value={newRow.capacity}
|
|
onChange={(e) => setNewRow({ ...newRow, capacity: e.target.value })}
|
|
/>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Chip
|
|
label="Available"
|
|
sx={{
|
|
backgroundColor: "#E7F4EE",
|
|
color: "#0D894F",
|
|
fontWeight: 600,
|
|
fontSize: "13px",
|
|
minWidth: 90,
|
|
}}
|
|
/>
|
|
</TableCell>
|
|
<TableCell>
|
|
<IconButton color="success" onClick={handleSaveRow}>
|
|
<SaveIcon />
|
|
</IconButton>
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
|
|
|
|
{paginatedRows.map((row) => (
|
|
<TableRow key={row.id} >
|
|
<TableCell sx={{ pl: { md: 9 } ,color:'#689dffff'}}>{row.table_number}</TableCell>
|
|
<TableCell>{row.capacity}</TableCell>
|
|
<TableCell>
|
|
<Chip {...getStatusChipProps(row.status)} />
|
|
</TableCell>
|
|
<TableCell>
|
|
<IconButton color="error" onClick={() => handleDeleteRow(row)}>
|
|
<DeleteOutlineIcon />
|
|
</IconButton>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
{/* Pagination Footer */}
|
|
<Box
|
|
display="flex"
|
|
justifyContent="space-between"
|
|
alignItems="center"
|
|
pt={2}
|
|
>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Showing {(currentPage - 1) * itemsPerPage + 1} -{" "}
|
|
{Math.min(currentPage * itemsPerPage, filteredRows.length)} of {filteredRows.length}
|
|
</Typography>
|
|
<SimplePagination currentPage={currentPage} pageCount={pageCount} onChange={setCurrentPage} />
|
|
</Box>
|
|
|
|
{/* Dialog الحذف */}
|
|
<Dialog open={confirmDeleteOpen} onClose={() => setConfirmDeleteOpen(false)}>
|
|
<DialogTitle>Confirm Delete</DialogTitle>
|
|
<DialogContent>
|
|
<Typography>
|
|
Are you sure you want to delete table "{rowToDelete?.table_number}"?
|
|
</Typography>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setConfirmDeleteOpen(false)}>Cancel</Button>
|
|
<Button
|
|
onClick={handleConfirmDelete}
|
|
color="error"
|
|
variant="contained"
|
|
disabled={isSaving}
|
|
>
|
|
{isSaving ? "Deleting..." : "Delete"}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default TablesManager;
|