seating chart edit

This commit is contained in:
2026-02-10 15:04:12 -05:00
parent 4295d1bc0f
commit 4ef2ebae2c
20 changed files with 202 additions and 76 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

View File

@@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.57cff37a.css",
"main.js": "/static/js/main.68aeca0a.js",
"main.css": "/static/css/main.6616f3cb.css",
"main.js": "/static/js/main.0e292118.js",
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
"index.html": "/index.html",
"main.57cff37a.css.map": "/static/css/main.57cff37a.css.map",
"main.68aeca0a.js.map": "/static/js/main.68aeca0a.js.map",
"main.6616f3cb.css.map": "/static/css/main.6616f3cb.css.map",
"main.0e292118.js.map": "/static/js/main.0e292118.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
},
"entrypoints": [
"static/css/main.57cff37a.css",
"static/js/main.68aeca0a.js"
"static/css/main.6616f3cb.css",
"static/js/main.0e292118.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.68aeca0a.js"></script><link href="/static/css/main.57cff37a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.0e292118.js"></script><link href="/static/css/main.6616f3cb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
client/.DS_Store vendored

Binary file not shown.

View File

@@ -1651,11 +1651,11 @@ input[type="checkbox"] {
.label-delete-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
border: 1px solid #ccc;
padding: 8px;
padding: 8px 12px;
border-radius: 8px;
margin-bottom: 6px;
}
.btn-custom-label {
@@ -1732,14 +1732,15 @@ input[type="checkbox"] {
.manage-seating-chart-container {
background: #D0E1F8;
/* right: -250px; */
/* top: 0; */
height: 100vh;
width: 300px;
min-width: 300px;
flex-shrink: 0;
padding: 16px;
overflow: auto;
margin-top: -40px;
margin-left: 40px;
position: sticky;
top: 0;
margin-left: 24px;
}
.manage-seating-chart-title-container {
@@ -1768,6 +1769,11 @@ input[type="checkbox"] {
align-items: flex-start;
}
.seating-page-container > .list.row {
flex: 1;
min-width: 0;
}
.table-config-container {
margin-bottom: 4px;
}

View File

@@ -121,6 +121,9 @@ const EventsCalendar = () => {
const [editRepeatEndDate, setEditRepeatEndDate] = useState(null);
const [editIndefiniteRepeat, setEditIndefiniteRepeat] = useState(false);
const [editingRecurId, setEditingRecurId] = useState(null); // tracks if editing a recurrence rule
// Delete confirmation modal
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleteTargetId, setDeleteTargetId] = useState(null);
// Helper function to format name from "lastname, firstname" to "firstname lastname"
const formatFullName = (name) => {
@@ -818,12 +821,12 @@ const EventsCalendar = () => {
style={{ cursor: 'pointer' }}
title="Edit"
/>
<Archive
size={16}
onClick={() => disableEvent(calendarEvent?.id)}
style={{ cursor: 'pointer' }}
title="Delete"
/>
<Archive
size={16}
onClick={() => { setDeleteTargetId(calendarEvent?.id); setShowDeleteConfirm(true); }}
style={{ cursor: 'pointer' }}
title="Delete"
/>
</div>
</>
}
@@ -2088,6 +2091,24 @@ const getReminderTitleLabel = (value) => {
</Button>
</Modal.Footer>
</Modal>
{/* Delete Confirmation Modal */}
<Modal show={showDeleteConfirm} onHide={() => { setShowDeleteConfirm(false); setDeleteTargetId(null); }} size="sm" centered>
<Modal.Header closeButton>
<Modal.Title>Confirm Delete</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete this event?
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" size="sm" onClick={() => { setShowDeleteConfirm(false); setDeleteTargetId(null); }}>
Cancel
</Button>
<Button variant="danger" size="sm" onClick={() => { disableEvent(deleteTargetId); setShowDeleteConfirm(false); setDeleteTargetId(null); }}>
Delete
</Button>
</Modal.Footer>
</Modal>
</div>
</div>
</>

View File

@@ -98,6 +98,8 @@ const Seating = () => {
const [startAddLabel, setStartAddLabel] = useState(false);
const [deletedItems, setDeletedItems] = useState([]);
const [editingLabelId, setEditingLabelId] = useState(null);
const [editingLabelName, setEditingLabelName] = useState('');
const [selectedCustomer, setSelectedCustomer] = useState(undefined);
const [selectedCustomerLabel, setSelectedCustomerLabel] = useState(undefined);
@@ -550,6 +552,8 @@ const Seating = () => {
setCurrentLabels(originalLabelsList);
setShowLabelConfig(false);
setStartAddLabel(false);
setEditingLabelId(null);
setEditingLabelName('');
}
const onDeleteLabel = (item) => {
@@ -562,6 +566,28 @@ const Seating = () => {
]))
}
const startEditingLabel = (item) => {
setEditingLabelId(item.id);
setEditingLabelName(item.label_name);
}
const saveEditingLabel = () => {
if (!editingLabelId || !editingLabelName.trim()) return;
LabelService.updateLabel(editingLabelId, { label_name: editingLabelName.trim() }).then(() => {
LabelService.getAll('active').then(data => {
setCurrentLabels(data?.data);
setOriginalLabelsList(data?.data);
});
});
setEditingLabelId(null);
setEditingLabelName('');
}
const cancelEditingLabel = () => {
setEditingLabelId(null);
setEditingLabelName('');
}
const handleFullScreen = () => {
setIsFullScreen(true);
}
@@ -997,7 +1023,29 @@ const Seating = () => {
<h6>Customer Labels</h6>
<div className="mb-4">
{
currentLabels.map((item) => <div className="label-delete-item"><span style={{width: '16px', height: '16px', borderRadius: '16px', background: item.label_color }}></span>{item.label_name} <FileX size={16} onClick={(item) => onDeleteLabel(item)}></FileX></div>)
currentLabels.map((item) => <div className="label-delete-item" key={item.id}>
<span style={{width: '16px', height: '16px', borderRadius: '16px', background: item.label_color, flexShrink: 0 }}></span>
{editingLabelId === item.id ? (
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', flex: 1, marginLeft: '8px' }}>
<input
type="text"
value={editingLabelName}
onChange={(e) => setEditingLabelName(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') saveEditingLabel(); if (e.key === 'Escape') cancelEditingLabel(); }}
style={{ flex: 1, fontSize: '13px', padding: '2px 6px', border: '1px solid #aaa', borderRadius: '4px' }}
autoFocus
/>
<button className="btn btn-primary btn-sm" style={{ padding: '1px 8px', fontSize: '12px' }} onClick={saveEditingLabel}>OK</button>
<button className="btn btn-default btn-sm" style={{ padding: '1px 8px', fontSize: '12px' }} onClick={cancelEditingLabel}>X</button>
</div>
) : (
<>
<span style={{ flex: 1, marginLeft: '8px' }}>{item.label_name}</span>
<PencilSquare size={14} color="#777" style={{ cursor: 'pointer', marginRight: '8px' }} onClick={() => startEditingLabel(item)} />
<FileX size={16} style={{ cursor: 'pointer' }} onClick={() => onDeleteLabel(item)} />
</>
)}
</div>)
}
</div>
{!startAddLabel && <button className="btn btn-tertiary btn-custom-label btn-sm mb-4" onClick={() => setStartAddLabel(true)}>+ Add New Label</button>}

View File

@@ -1,22 +1,19 @@
import React, { useEffect, useState } from "react";
import { TransRoutesService } from "../../services";
import { useDispatch } from "react-redux";
import { transRoutesSlice, selectBreakfastsLoaded } from "./../../store";
import moment from 'moment';
const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHasBreakfast, removeBreakfastRecord}) => {
const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHasBreakfast, removeBreakfastRecord, selectedDate, refreshRecords}) => {
const [customers, setCustomers] = useState([]);
const dispatch = useDispatch();
const { fetchAllBreakfastRecords } = transRoutesSlice.actions;
const createBreakfastForAll = async () => {
const dateStr = moment(selectedDate || new Date()).format('MM/DD/YYYY');
for (const c of customers) {
await TransRoutesService.createBreakfastRecords({
customer_id: c?.customer_id,
customer_name: c?.customer_name,
has_breakfast: true,
date: moment(new Date()).format('MM/DD/YYYY'),
date: dateStr,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
@@ -25,7 +22,7 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
}]
})
};
dispatch(fetchAllBreakfastRecords());
if (refreshRecords) refreshRecords();
}
useEffect(() => {

View File

@@ -1,22 +1,19 @@
import React, { useEffect, useState } from "react";
import { TransRoutesService } from "../../services";
import { useDispatch } from "react-redux";
import { transRoutesSlice } from "./../../store";
import moment from 'moment';
const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch, removeLunchRecord}) => {
const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch, removeLunchRecord, selectedDate, refreshRecords}) => {
const [customers, setCustomers] = useState([]);
const dispatch = useDispatch();
const { fetchAllLunchRecords } = transRoutesSlice.actions;
const createLunchForAll = async () => {
const dateStr = moment(selectedDate || new Date()).format('MM/DD/YYYY');
for (const c of customers) {
await TransRoutesService.createLunchRecords({
customer_id: c?.customer_id,
customer_name: c?.customer_name,
has_lunch: true,
date: moment(new Date()).format('MM/DD/YYYY'),
date: dateStr,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
@@ -25,7 +22,7 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
}]
})
};
dispatch(fetchAllLunchRecords());
if (refreshRecords) refreshRecords();
}
useEffect(() => {

View File

@@ -1,6 +1,4 @@
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { selectAllRoutes, selectAllBreakfastRecords, selectAllLunchRecords, selectAllSnackRecords, transRoutesSlice } from "./../../store";
import React, { useEffect, useState, useCallback } from "react";
import { TransRoutesService } from "../../services";
import BreakfastSection from './BreakfastSection';
import LunchSection from "./LunchSection";
@@ -9,26 +7,62 @@ import { Breadcrumb } from "react-bootstrap";
import moment from 'moment';
const MealStatus = () => {
const dispatch = useDispatch();
const { fetchAllRoutes, fetchAllBreakfastRecords, fetchAllLunchRecords, fetchAllSnackRecords } = transRoutesSlice.actions;
const allRoutes = useSelector(selectAllRoutes);
const breakfastRecords = useSelector(selectAllBreakfastRecords);
const lunchRecords = useSelector(selectAllLunchRecords);
const snackRecords = useSelector(selectAllSnackRecords);
const [selectedDate, setSelectedDate] = useState(new Date());
const [allRoutes, setAllRoutes] = useState([]);
const [breakfastRecords, setBreakfastRecords] = useState([]);
const [lunchRecords, setLunchRecords] = useState([]);
const [snackRecords, setSnackRecords] = useState([]);
const dateText = moment(selectedDate).format('MM/DD/YYYY');
const fetchAllData = useCallback(() => {
const formattedDate = moment(selectedDate).format('MM/DD/YYYY');
TransRoutesService.getAll(formattedDate).then(res => {
setAllRoutes(res?.data || []);
}).catch(() => setAllRoutes([]));
TransRoutesService.getAllBreakfastRecords(formattedDate).then(res => {
setBreakfastRecords(res?.data || []);
}).catch(() => setBreakfastRecords([]));
TransRoutesService.getAllLunchRecords(formattedDate).then(res => {
setLunchRecords(res?.data || []);
}).catch(() => setLunchRecords([]));
TransRoutesService.getAllSnackRecords(formattedDate).then(res => {
setSnackRecords(res?.data || []);
}).catch(() => setSnackRecords([]));
}, [selectedDate]);
useEffect(() => {
dispatch(fetchAllRoutes());
dispatch(fetchAllBreakfastRecords());
dispatch(fetchAllLunchRecords());
dispatch(fetchAllSnackRecords());
}, []);
fetchAllData();
}, [fetchAllData]);
const goToPreviousDay = () => {
setSelectedDate(prev => {
const d = new Date(prev);
d.setDate(d.getDate() - 1);
return d;
});
};
const goToNextDay = () => {
setSelectedDate(prev => {
const d = new Date(prev);
d.setDate(d.getDate() + 1);
return d;
});
};
const goToToday = () => {
setSelectedDate(new Date());
};
const isToday = moment(selectedDate).isSame(moment(), 'day');
const confimHasBreakfast = (customer) => {
TransRoutesService.createBreakfastRecords({
customer_id: customer?.customer_id,
customer_name: customer?.customer_name,
has_breakfast: true,
date: moment(new Date()).format('MM/DD/YYYY'),
date: dateText,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
@@ -36,7 +70,7 @@ const MealStatus = () => {
date: new Date()
}]
}).then(() => {
dispatch(fetchAllBreakfastRecords());
fetchAllData();
})
}
@@ -45,7 +79,7 @@ const MealStatus = () => {
customer_id: customer?.customer_id,
customer_name: customer?.customer_name,
has_lunch: true,
date: moment(new Date()).format('MM/DD/YYYY'),
date: dateText,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
@@ -53,7 +87,7 @@ const MealStatus = () => {
date: new Date()
}]
}).then(() => {
dispatch(fetchAllLunchRecords());
fetchAllData();
})
}
@@ -62,7 +96,7 @@ const MealStatus = () => {
customer_id: customer?.customer_id,
customer_name: customer?.customer_name,
has_snack: true,
date: moment(new Date()).format('MM/DD/YYYY'),
date: dateText,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
@@ -70,28 +104,28 @@ const MealStatus = () => {
date: new Date()
}]
}).then(() => {
dispatch(fetchAllSnackRecords());
fetchAllData();
})
}
const removeBreakfastRecord = (customer_id) => {
const breakfast = breakfastRecords.find(b => b.customer_id === customer_id)?.id;
TransRoutesService.deleteBreakfastRecords(breakfast).then(() => {
dispatch(fetchAllBreakfastRecords());
fetchAllData();
})
}
const removeLunchRecord = (customer_id) => {
const lunch = lunchRecords.find(b => b.customer_id === customer_id)?.id;
TransRoutesService.deleteLunchRecords(lunch).then(() => {
dispatch(fetchAllLunchRecords());
fetchAllData();
})
}
const removeSnackRecord = (customer_id) => {
const snack = snackRecords.find(b => b.customer_id === customer_id)?.id;
TransRoutesService.deleteSnackRecords(snack).then(() => {
dispatch(fetchAllSnackRecords());
fetchAllData();
})
}
@@ -107,9 +141,27 @@ const MealStatus = () => {
<div className="col-md-12 text-primary">
<h4>Meal Status</h4>
</div>
<div className="col-md-12 d-flex align-items-center" style={{ marginTop: '8px' }}>
<div className="d-flex align-items-center" style={{ gap: '12px' }}>
<button className="btn btn-outline-secondary btn-sm" onClick={goToPreviousDay} title="Previous day">
<span style={{ fontSize: '18px', lineHeight: 1 }}>&lsaquo;</span>
</button>
<span style={{ fontWeight: 600, fontSize: '16px', minWidth: '160px', textAlign: 'center' }}>
{moment(selectedDate).format('ddd, MMM D, YYYY')}
</span>
<button className="btn btn-outline-secondary btn-sm" onClick={goToNextDay} title="Next day">
<span style={{ fontSize: '18px', lineHeight: 1 }}>&rsaquo;</span>
</button>
{!isToday && (
<button className="btn btn-outline-primary btn-sm" onClick={goToToday}>
Today
</button>
)}
</div>
</div>
</div>
<div className="app-main-content-list-container">
<div className="app-main-content-list-container" style={{ marginTop: '16px' }}>
<div className="list row">
<div className="col-md-4 col-sm-12 mb-4">
<BreakfastSection
@@ -118,6 +170,8 @@ const MealStatus = () => {
confimHasBreakfast={confimHasBreakfast}
removeBreakfastRecord={removeBreakfastRecord}
sectionName={'Breakfast Info'}
selectedDate={selectedDate}
refreshRecords={fetchAllData}
/>
</div>
<div className="col-md-4 col-sm-12 mb-4">
@@ -127,6 +181,8 @@ const MealStatus = () => {
confirmHasLunch={confirmHasLunch}
removeLunchRecord={removeLunchRecord}
sectionName={'Lunch Info'}
selectedDate={selectedDate}
refreshRecords={fetchAllData}
/>
</div>
<div className="col-md-4 col-sm-12 mb-4">
@@ -136,6 +192,8 @@ const MealStatus = () => {
confirmHasSnack={confirmHasSnack}
removeSnackRecord={removeSnackRecord}
sectionName={'Snack Info'}
selectedDate={selectedDate}
refreshRecords={fetchAllData}
/>
</div>
</div>

View File

@@ -405,14 +405,16 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
// Only initialize customers from currentRoute when the route ID changes
// Don't reset when currentRoute object reference changes (which happens on every render)
// Note: template sub-document routes use _id instead of id
const currentRouteId = currentRoute?.id || currentRoute?._id;
useEffect(() => {
if (currentRoute?.id && currentRoute.id !== initializedRouteId) {
if (currentRouteId && currentRouteId !== initializedRouteId) {
setCustomers(getRouteCustomersWithGroups());
setInitializedRouteId(currentRoute.id);
setInitializedRouteId(currentRouteId);
}
}, [currentRoute?.id, initializedRouteId])
}, [currentRouteId, initializedRouteId])
const getRouteCustomersWithGroups = () => {
const customerList = currentRoute?.route_customer_list?.map(item => Object.assign({}, item, {routeType: currentRoute.type, routeId: currentRoute.id}));
const customerList = currentRoute?.route_customer_list?.map(item => Object.assign({}, item, {routeType: currentRoute.type, routeId: currentRoute.id || currentRoute._id}));
const result = {};
if (customerList) {
for (const customer of customerList) {

View File

@@ -1,22 +1,19 @@
import React, { useEffect, useState } from "react";
import { TransRoutesService } from "../../services";
import { useDispatch } from "react-redux";
import { transRoutesSlice } from "./../../store";
import moment from 'moment';
const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack, removeSnackRecord}) => {
const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack, removeSnackRecord, selectedDate, refreshRecords}) => {
const [customers, setCustomers] = useState([]);
const dispatch = useDispatch();
const { fetchAllSnackRecords } = transRoutesSlice.actions;
const createSnackForAll = async () => {
const dateStr = moment(selectedDate || new Date()).format('MM/DD/YYYY');
for (const c of customers) {
await TransRoutesService.createSnackRecords({
customer_id: c?.customer_id,
customer_name: c?.customer_name,
has_snack: true,
date: moment(new Date()).format('MM/DD/YYYY'),
date: dateStr,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
@@ -25,7 +22,7 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
}]
})
};
dispatch(fetchAllSnackRecords());
if (refreshRecords) refreshRecords();
}
useEffect(() => {