2025-07-04 14:13:25 -04:00

690 lines
23 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import CircularTable from './CircularTable';
import { Breadcrumb, Tabs, Tab, Dropdown } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, ArrowBarRight, ChevronDown, ChevronRight, FileX } from "react-bootstrap-icons";
import { CustomerService, LabelService, SeatingService } from '../../services';
import moment from 'moment';
import Select from 'react-select';
const Seating = () => {
const initialValue = {
rows: [{
id: 1,
tables: 1
}],
tables: {
'table-1': {
id: 1,
tableId: 'table-1',
seats: [
{
id: '1-A',
name: 'A',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-B',
name: 'B',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-C',
name: 'C',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-D',
name: 'D',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-E',
name: 'E',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-F',
name: 'F',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-G',
name: 'G',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
},
{
id: '1-H',
name: 'H',
customerId: '',
customerName: '',
label: {},
rowIndex: 1
}
],
expanded: false,
rowIndex: 1
}
}
};
const [tableLayout, setTableLayout] = useState({
rows: [],
tables: {}
}
// rows: [
// {
// itemsLength: 5,
// id: 'row-1'
// }, {
// itemsLength: 5,
// id: 'row-2'
// }, {
// itemsLength: 5,
// id: 'row-3'
// }
// ],
// tables:
);
const [rowCount, setRowCount] = useState(1);
const [currentDateRecord, setCurrentDateRecord] = useState(null);
const [tableCountsPerRow, setTableCountsPerRow] = useState([1]);
const [editingSeat, setEditingSeat] = useState(null);
const [showTableLayoutConfig, setShowTableLayoutConfig] = useState(false);
const [currentLabels, setCurrentLabels] = useState([]);
const [newLabel, setNewLabel] = useState({ label_name: undefined, label_color: undefined});
const [customers, setCustomers] = useState([]);
const [showLabelConfig, setShowLabelConfig] = useState(false);
const [originalLabelsList, setOriginalLabelsList] = useState([]);
const [startAddLabel, setStartAddLabel] = useState(false);
const [deletedItems, setDeletedItems] = useState([]);
const [selectedCustomer, setSelectedCustomer] = useState(undefined);
const [selectedCustomerLabel, setSelectedCustomerLabel] = useState(undefined);
const [selectedLabelColor, setSelectedLabelColor] = useState(undefined);
const colorsList = ['#AD967A', '#BD816E', '#2B76E5', '#66CCFF', '#0A7E22', '#00C875', '#9CD326', '#FFCB00', '#FF642E', '#FD314D', '#BB3354', '#FF158A', '#9B51E0', '#BDA8F9'];
useEffect(() => {
CustomerService.getAllActiveCustomers().then(data => setCustomers(data?.data));
LabelService.getAll('active').then((data) => {setCurrentLabels(data?.data); setOriginalLabelsList(data?.data)});
SeatingService.getAll(moment().format('MM/DD/YYYY')).then(data => {
const result = data?.data;
if (result?.length > 0) {
setTableLayout(result[0]?.seating_assignment);
setCurrentDateRecord(result[0]);
setRowCount(result[0]?.seating_assignment?.rows?.length);
setTableCountsPerRow(result[0]?.seating_assignment?.rows?.map(item => item.tables));
}
});
}, [])
useEffect(() => {
const newRows = Array.from({length: rowCount}, (_, index) => ({
id: index+1,
tables: tableCountsPerRow[index] || 0
}));
if (rowCount !== tableLayout.rows?.length) {
setTableLayout(prevLayout => ({
...prevLayout,
rows: newRows
}));
if (tableCountsPerRow.length !== rowCount) {
setTableCountsPerRow(prev => {
const newCounts = [...prev];
while(newCounts.length < newCounts) {
newCounts.push(0);
}
return newCounts.slice(0, rowCount);
})
}
}
}, [rowCount]);
useEffect(() => {
const newTables = {};
let tableNumber = 1;
let startUpdate = false;
if (tableCountsPerRow.length !== tableLayout.rows.length) {
startUpdate = true;
} else {
for (let i = 0; i<tableLayout?.rows.length; i++) {
if (tableLayout.rows[i].tables !== tableCountsPerRow[i]) {
startUpdate = true;
break;
}
}
}
if (startUpdate) {
tableLayout.rows.forEach((row, rowIndex) => {
const tableCount = tableCountsPerRow[rowIndex] || 0;
row.tables = tableCount;
for (let tableIndex = 0; tableIndex < tableCount; tableIndex++) {
const tableId = `table-${tableNumber}`
if (!newTables[tableId]) {
const seats = Array.from({length: 8}, (_, seatIndex) => {
const seatLetter = String.fromCharCode(65 + seatIndex);
return {
id: `${tableNumber}-${seatLetter}`,
name: seatLetter,
customerName: '',
customerId: '',
rowIndex: rowIndex + 1,
label: {label_name: undefined, label_color: undefined}
}
});
newTables[tableId] = {
id: tableNumber,
tableId: tableId,
seats,
expanded: false,
rowIndex: rowIndex + 1
}
} else {
newTables[tableId] = tableLayout.tables[tableId];
}
tableNumber++;
}
})
setTableLayout(prevLayout => ({
...prevLayout,
rows: prevLayout.rows.map((item, index) => ({...item, tables: tableCountsPerRow[index]}) ),
tables: newTables
}));
}
}, [tableCountsPerRow, tableLayout.rows]);
const handleRowCountChange = (e) => {
const count = parseInt(e.target.value) || 0;
setRowCount(count);
}
const handleTableCountChange = (rowIndex, count) => {
setTableCountsPerRow(prev => {
const newCounts = [...prev];
newCounts[rowIndex] = parseInt(count) || 0;
return newCounts;
})
}
const toggleTableExpansion = (tableId) => {
setTableLayout(prevLayout => ({
...prevLayout,
tables: {
...prevLayout.tables,
[tableId]: {
...prevLayout.tables[tableId],
expanded: !prevLayout.tables[tableId].expanded
}
}
}));
}
const cleanAndClose = () => {
setTableLayout(currentDateRecord?.seat_assignment || initialValue);
setRowCount(currentDateRecord?.seat_assignment?.rows?.length || 1);
setTableCountsPerRow(currentDateRecord?.seat_assignment?.rows?.map(item => item.tables));
setShowTableLayoutConfig(false);
setSelectedCustomer(undefined);
setSelectedCustomerLabel(undefined);
}
const saveAndClose = () => {
if (currentDateRecord) {
SeatingService.updateSeating(currentDateRecord?.id, { seating_assignment: tableLayout, update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, update_date: new Date() })
} else {
SeatingService.createNewSeating({
date: moment().format('MM/DD/YYYY'),
seating_assignment: tableLayout,
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
update_date: new Date()
}).then((data) => {
const result = data?.data;
setCurrentDateRecord(result);
})
}
setShowTableLayoutConfig(false);
setSelectedCustomer(undefined);
setSelectedCustomerLabel(undefined);
}
const [keyword, setKeyword] = useState('');
const getTotalTableNumber = () => tableCountsPerRow.reduce((acc, curr) => acc + curr, 0);
const tablesByRow = () => tableLayout.rows.map((row, rowIndex) => {
const rowTables = Object.values(tableLayout.tables).filter(table => table.rowIndex === rowIndex + 1).sort((a,b) => a.id - b.id);
return {
rowId: row.id,
tables: rowTables
}
})
const startEditingSeat = (tableId, seatId) => {
const seat = tableLayout.tables[tableId].seats.find(seat => seat.id === seatId);
setEditingSeat({
tableId,
seatId,
customerId: seat?.customerId,
label: seat?.label,
customerName: seat?.customerName
});
setSelectedCustomer({value: seat?.customerId, label: seat?.customerName});
setSelectedCustomerLabel({value: seat?.label?.id, label: seat?.label?.label_name});
};
const onCustomerChange = (selectedCustomer) => {
setEditingSeat({
...editingSeat,
customerId: selectedCustomer.value,
customerName: selectedCustomer.label
});
setSelectedCustomer(selectedCustomer)
}
const handleLabelChange = (selectedLabel) => {
setEditingSeat({
...editingSeat,
label: { label_name: selectedLabel.label, id: selectedLabel.value, label_color: currentLabels.find(item => item.id === selectedLabel.value)?.label_color }
})
setSelectedCustomerLabel(selectedLabel);
}
const handleNewLabelNameChange = (e) => {
setNewLabel(prevLabel => ({
...prevLabel,
label_name: e.target.value
}))
}
const handleNewLabelColorChange = (selectedColor) => {
setSelectedLabelColor(selectedColor);
setNewLabel(prevLabel => ({
...prevLabel,
label_color: selectedColor.value
}))
}
const saveSeatEdit = () =>{
if (!editingSeat) return;
const {tableId, seatId, customerName, customerId, label} = editingSeat;
setTableLayout(prevLayout => {
const updatedSeats = prevLayout.tables[tableId].seats.map(seat => seat.id === seatId ? {...seat, customerName, customerId, label} : seat);
const finalResult = {
...prevLayout,
tables: {
...prevLayout.tables,
[tableId]: {
...prevLayout.tables[tableId],
seats: updatedSeats
}
}
};
SeatingService.updateSeating(currentDateRecord?.id, { seating_assignment: finalResult, update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, update_date: new Date()});
CustomerService.updateCustomer(customerId, { table_id: tableId, seating: seatId, tags:[...customers.find(item => item.id === customerId)?.tags, label?.label_name], edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, edit_date: new Date()})
return finalResult;
});
setEditingSeat(null);
}
const cancelSeatEdit = () => {
setEditingSeat(null);
}
const saveLabelAndClose = () => {
if (newLabel.label_color && newLabel.label_name) {
LabelService.createNewLabel(newLabel).then(() => {
LabelService.getAll('active').then(data => {
setCurrentLabels(data?.data);
setOriginalLabelsList(data?.data);
setShowLabelConfig(false);
setStartAddLabel(false);
setNewLabel({ label_color: undefined, label_name: undefined})
});
});
}
if (deletedItems.length > 0) {
Promise.all(deletedItems.map(item => LabelService.updateLabel(item.id, {status: 'inactive'}))).then(() => setDeletedItems([]));
}
}
const cleanLabelAndClose = () => {
setCurrentLabels(originalLabelsList);
setShowLabelConfig(false);
setStartAddLabel(false);
}
const onDeleteLabel = (item) => {
setCurrentLabels(prevLabels => ({
...prevLabels.filter(lb => lb.id !== item.id)
}))
setDeletedItems(prevLabels => ([
...prevLabels,
item
]))
}
return (
<div className="seating-page-container">
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item>Lobby</Breadcrumb.Item>
<Breadcrumb.Item active>
Seating Chart
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
Seating Chart
</h4>
</div>
<div className="app-main-content-list-container">
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="seatingChart" id="seatingTabs">
<Tab eventKey="seatingChart" title="Seating Chart">
<div className="seating-chart-container">
<div className="seating-stage">
<div className="stage">Stage</div>
</div>
{
tablesByRow().map((rowItem, rowIndex) => {
return (<div className="seating-row-container" id={`seating-row-${rowIndex}`} key={`seating-row-${rowIndex}`}>
{
rowItem?.tables?.map((item, itemIndex) => <CircularTable className="me-4" key={`seating-table-${rowIndex}-${itemIndex}`} id={`seating-table-${rowIndex}-${itemIndex}`} tableNumber={item?.id} guests={item?.seats} />)
}
</div>)
})
}
<div className="seating-stage mt-4">
{
currentLabels.map((item) => <div><span style={{width: '16px', height: '16px', borderRadius: '16px', display: 'inline-block', background: item.label_color, marginRight: '8px' }}></span>{item.label_name}</div>)
}
</div>
</div>
</Tab>
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button>
<button className="btn btn-primary me-2"><Columns size={16} className="me-2"></Columns>Manage Labels</button>
{/* <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button> */}
</div>
</div>
</div>
</div>
<div className="manage-seating-chart-container">
<div className="manage-seating-chart-title-container">
<h6>Manage Seating Chart</h6>
{/* <ArrowBarRight color="#777" size={20}></ArrowBarRight> */}
</div>
<div className="manage-seating-chart-tables-container">
<div className="title">
<strong>Table Layout</strong>
<PencilSquare color="#777" size={16} onClick={() => setShowTableLayoutConfig(true)}/>
{
showTableLayoutConfig && <div className="seating-popover">
<h6>Manage Table Layout</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Number of Rows</div>
<input type="number" min="0" value={rowCount} onChange={handleRowCountChange}></input>
</div>
</div>
{
rowCount>0 && Array.from({length: rowCount}).map((_, rowIndex) => (
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">{`Row ${rowIndex + 1}`}</div>
<input type="number" min="0" value={tableCountsPerRow[rowIndex] || 0} onChange={(e) => handleTableCountChange(rowIndex, e.target.value)}/>
</div>
</div>
))
}
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={() => cleanAndClose()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => saveAndClose()}> Save </button>
</div>
</div>
</div>
}
</div>
{
Object.keys(tableLayout.tables).map((tableId) => (
<div className="table-config-container">
<div className="table-config-item-title">
{!tableLayout.tables[tableId]?.expanded && <ChevronDown className="me-2" color="#777" size={12} onClick={() => toggleTableExpansion(tableId)} />}
{tableLayout.tables[tableId]?.expanded && <ChevronRight className="me-2" color="#777" size={12} onClick={() => toggleTableExpansion(tableId)} />}
{tableId.replace('-', ' ')}
</div>
{tableLayout.tables[tableId]?.expanded && tableLayout.tables[tableId]?.seats.map(seat => <div className="table-config-item">
{`${seat.name}. ${seat.customerName}`} <PencilSquare color="#777" size={16} onClick={() => startEditingSeat(tableId, seat.id)} />
{
editingSeat && editingSeat.tableId === tableId && editingSeat.seatId === seat.id && <div className="seating-popover">
<h6>Seat Assignment</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Customer Name</div>
<Select styles={{
control: (baseStyles, state) => ({
...baseStyles,
width: '210px',
height: '35px',
'padding-top': 0,
'padding-bottom': 0,
'margin-top': 0,
'margin-bottom': 0
}),
indicatorSeparator: (baseStyles, state) => ({
...baseStyles,
width: 0
}),
indicatorsContainer: (baseStyles) => ({
...baseStyles,
'margin-top': '-10px'
}),
placeholder: (baseStyles) => ({
...baseStyles,
'margin-top': '-10px',
'font-size': '13px'
}),
singleValue: (baseStyles, state) => ({
...baseStyles,
'margin-top': '-10px',
'font-size': '13px'
})
}} value={selectedCustomer} onChange={newSelectedCustomer => onCustomerChange(newSelectedCustomer)} options={[{value: '', label: ''}, ...customers.map(customer => ({
value: customer?.id || '',
label: customer?.name || ''
}))]}></Select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Customer Label</div>
<Select styles={{
control: (baseStyles, state) => ({
...baseStyles,
width: '210px',
height: '35px',
'padding-top': 0,
'padding-bottom': 0,
'margin-top': 0,
'margin-bottom': 0
}),
indicatorSeparator: (baseStyles, state) => ({
...baseStyles,
width: 0
}),
indicatorsContainer: (baseStyles) => ({
...baseStyles,
'margin-top': '-10px'
}),
placeholder: (baseStyles) => ({
...baseStyles,
'margin-top': '-10px',
'font-size': '13px'
}),
singleValue: (baseStyles, state) => ({
...baseStyles,
'margin-top': '-10px',
'font-size': '13px'
})
}} value={ selectedCustomerLabel} onChange={selectedLabel => handleLabelChange(selectedLabel)} options={[{value: '', label: ''}, ...currentLabels.map(label => ({
value: label?.id|| '',
label: label?.label_name || ''
}))]}></Select>
</div>
</div>
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={() => cancelSeatEdit()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => saveSeatEdit()}> Save </button>
</div>
</div>
</div>
}
</div>)}
</div>
))
}
<div className="title">
<strong>Customer Label</strong>
<PencilSquare color="#777" size={16} onClick={() => setShowLabelConfig(true)}/>
{
showLabelConfig && <div className="seating-popover">
<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>)
}
</div>
{!startAddLabel && <button className="btn btn-tertiary btn-custom-label btn-sm mb-4" onClick={() => setStartAddLabel(true)}>+ Add New Label</button>}
{startAddLabel && <>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Label Name</div>
<input type="text" value={newLabel.label_name} onChange={handleNewLabelNameChange}></input>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Label Color</div>
{/* <select value={newLabel.label_color} onChange={handleNewLabelColorChange}>
<option value=""></option>
{
colorsList.map((item) => <option style={{backgroundColor: item}} value={item}><div style={{backgroundColor: item, width: '4px', height: '4px'}}></div>{item}</option>)
}
</select> */}
<Select styles={{
control: (baseStyles, state) => ({
...baseStyles,
width: '210px',
height: '35px',
'padding-top': 0,
'padding-bottom': 0,
'margin-top': 0,
'margin-bottom': 0
}),
indicatorSeparator: (baseStyles, state) => ({
...baseStyles,
width: 0
}),
indicatorsContainer: (baseStyles) => ({
...baseStyles,
'margin-top': '-10px'
}),
placeholder: (baseStyles) => ({
...baseStyles,
'margin-top': '-10px',
'font-size': '13px'
}),
singleValue: (baseStyles, state) => ({
...baseStyles,
'margin-top': '-10px',
'font-size': '13px'
}),
option: (baseStyles, {data}) => ({
...baseStyles,
backgroundColor: data?.value
})
}} value={selectedLabelColor} onChange={selectedColor => handleNewLabelColorChange(selectedColor)} options={[{value: '', label: ''}, ...colorsList.map(color => ({
value: color|| '',
label: color || ''
}))]}></Select>
</div>
</div>
</>}
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={() => cleanLabelAndClose()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => saveLabelAndClose()}> Save </button>
</div>
</div>
</div>
}
</div>
{
currentLabels.map((item) => <div className="mb-4" style={{fontSize: '12px'}}><span style={{width: '16px', height: '16px', borderRadius: '16px', background: item.label_color, display: 'inline-block', marginRight: '8px' }}></span>{item.label_name}</div>)
}
</div>
</div>
</div>
)
}
export default Seating;