Seating Page

This commit is contained in:
Yang Li 2025-06-13 22:48:05 -04:00
parent f18f213f6b
commit 4237cc4a7c
33 changed files with 1579 additions and 30 deletions

View File

@ -0,0 +1,99 @@
const { splitSite } = require("../middlewares");
const db = require("../models");
const Label = db.label;
// Create a new Label Item
exports.createNewLabel = (req, res) => {
// Validate request
if (!req.body.label_name) {
res.status(400).send({ message: "Name can not be empty!" });
return;
}
const site = splitSite.findSiteNumber(req);
// Create an Label Item
const label = new Label({
label_name: req.body.label_name,
label_color: req.body.label_color,
status: 'active',
site
});
// Save label Item in the database
label
.save(label)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Label Record."
});
});
};
// Retrive all Label Records from database.
exports.getAllLabels = (req, res) => {
var params = req.query;
var condition = {};
if (params.status) {
condition.status = params.status;
}
condition = splitSite.splitSiteGet(req, condition);
Label.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Labels."
});
});
};
// Update a Label by the id in the request
exports.updateLabel = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const id = req.params.id;
Label.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update Label with id=${id}. Maybe Label was not found!`
});
} else res.send({ success: true, message: "Label was updated successfully." });
})
.catch(err => {
res.status(500).send({
success: false,
message: "Error updating Label with id=" + id
});
});
};
// Delete a Label by id
exports.deleteLabel= (req, res) => {
const id = req.params.id;
Label.findByIdAndRemove(id)
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete Label with id=${id}. Maybe Label was not found!`
});
} else {
res.send({
message: "Label was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Could not delete Label with id=" + id
});
});
};

View File

@ -0,0 +1,102 @@
const { splitSite } = require("../middlewares");
const db = require("../models");
const Seating = db.seating;
// Create a new Seating Item
exports.createNewSeating = (req, res) => {
// Validate request
if (!req.body.seating_assignment) {
res.status(400).send({ message: "Assignment can not be empty!" });
return;
}
const site = splitSite.findSiteNumber(req);
// Create an Seating Item
const seating = new Seating({
seating_assignment: req.body.seating_assignment,
date: req.body.date,
create_by: req.body.create_by,
create_date: req.body.create_date,
update_by: req.body.update_by,
update_date: req.body.update_date,
site
});
// Save seating Item in the database
seating
.save(seating)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Seating Record."
});
});
};
// Retrive all Seating Records from database.
exports.getAllSeatings = (req, res) => {
var params = req.query;
var condition = {};
if (params.date) {
condition.date = params.date;
}
condition = splitSite.splitSiteGet(req, condition);
Seating.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Seatings."
});
});
};
// Update a Seating by the id in the request
exports.updateSeating = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const id = req.params.id;
Seating.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update Seating with id=${id}. Maybe Seating was not found!`
});
} else res.send({ success: true, message: "Seating was updated successfully." });
})
.catch(err => {
res.status(500).send({
success: false,
message: "Error updating Seating with id=" + id
});
});
};
// Delete a Seating by id
exports.deleteSeating= (req, res) => {
const id = req.params.id;
Seating.findByIdAndRemove(id)
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete Seating with id=${id}. Maybe Seating was not found!`
});
} else {
res.send({
message: "Seating was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Could not delete Seating with id=" + id
});
});
};

View File

@ -0,0 +1,45 @@
const { splitSite } = require("../middlewares");
const db = require("../models");
const VehicleRepair = db.vehicle_repair;
exports.createVehicleRepair = (req, res) => {
if (!req.body.repair_description) {
res.status(400).send({ message: "Content can not be empty!" });
return;
}
const site = splitSite.findSiteNumber(req);
const vehicleRepair = new VehicleRepair({
repair_date: req.body.repair_date,
site,
repair_description: req.body.repair_description || '',
repair_price: req.body.repair_price || '',
repair_location: req.body.repair_location || '',
vehicle: req.body.vehicle,
create_date: new Date()
});
vehicleRepair.save(vehicleRepair).then(data => res.send(data)).catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while creating the Vehicle Repair Record."
})
})
}
exports.getAllVehicleRepairs = (req, res) => {
var condition = {};
const vehicle = req.query.vehicle;
condition = splitSite.splitSiteGet(req, condition);
condition.vehicle = vehicle;
VehicleRepair.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Vehicle Repair Records."
});
});
};

View File

@ -27,4 +27,7 @@ db.event_request = require("./event-request.model")(mongoose);
db.signature_request = require("./signature-request.model")(mongoose);
db.lunch = require("./lunch.model")(mongoose);
db.snack = require("./snack.model")(mongoose);
db.vehicle_repair = require("./vehicle-repair.model")(mongoose);
db.label = require("./label.model")(mongoose);
db.seating = require("./seating.model")(mongoose);
module.exports = db;

18
app/models/label.model.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = mongoose => {
var schema = mongoose.Schema(
{
label_name: String,
label_color: String,
site: Number,
status: String
},
{ collection: 'label', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
const Label = mongoose.model("label", schema);
return Label;
};

View File

@ -0,0 +1,21 @@
module.exports = mongoose => {
var schema = mongoose.Schema(
{
date: String,
seating_assignment: Object,
create_by: String,
create_date: Date,
update_by: String,
update_date: Date,
site: Number
},
{ collection: 'seating', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
const Seating = mongoose.model("seating", schema);
return Seating;
};

View File

@ -0,0 +1,22 @@
module.exports = mongoose => {
var schema = mongoose.Schema(
{
repair_date: String,
site: Number,
repair_description: String,
repair_price: String,
repair_location: String,
vehicle: String,
create_date: Date
},
{ collection: 'vehicle_repair', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
schema.index({tag: 1, site:1}, {unique: true});
const VehicleRepair = mongoose.model("vehicle_repair", schema);
return VehicleRepair;
};

View File

@ -0,0 +1,19 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const label = require("../controllers/label.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Retrieve all labels
router.get("/", [authJwt.verifyToken], label.getAllLabels);
// Create a new label
router.post("/", [authJwt.verifyToken], label.createNewLabel);
router.put('/:id', [authJwt.verifyToken], label.updateLabel);
router.delete('/:id', [authJwt.verifyToken], label.deleteLabel)
app.use('/api/labels', router);
};

View File

@ -0,0 +1,19 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const seating = require("../controllers/seating.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Retrieve all seatings
router.get("/", [authJwt.verifyToken],seating.getAllSeatings);
// Create a new seating
router.post("/", [authJwt.verifyToken], seating.createNewSeating);
router.put('/:id', [authJwt.verifyToken], seating.updateSeating);
router.delete('/:id', [authJwt.verifyToken], seating.deleteSeating)
app.use('/api/seatings', router);
};

View File

@ -0,0 +1,17 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const vehicleRepairs = require("../controllers/vehicle-repair.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Retrieve all vehicle records
router.get("/", [authJwt.verifyToken], vehicleRepairs.getAllVehicleRepairs);
// Create a new vehicle record
router.post("/", [authJwt.verifyToken], vehicleRepairs.createVehicleRepair);
app.use('/api/vehicle-repairs', router);
};

View File

@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.e5412702.css",
"main.js": "/static/js/main.26b7d753.js",
"main.css": "/static/css/main.11c89bb3.css",
"main.js": "/static/js/main.1ecf349a.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.e5412702.css.map": "/static/css/main.e5412702.css.map",
"main.26b7d753.js.map": "/static/js/main.26b7d753.js.map",
"main.11c89bb3.css.map": "/static/css/main.11c89bb3.css.map",
"main.1ecf349a.js.map": "/static/js/main.1ecf349a.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
},
"entrypoints": [
"static/css/main.e5412702.css",
"static/js/main.26b7d753.js"
"static/css/main.11c89bb3.css",
"static/js/main.1ecf349a.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="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.26b7d753.js"></script><link href="/static/css/main.e5412702.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="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.1ecf349a.js"></script><link href="/static/css/main.11c89bb3.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

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,14 @@ body {
color: #0066B1;
}
.btn-tertiary {
background: white;
color: #333;
width: 100%;
border: 1px solid #ccc;
border-radius: 6px;
}
.breadcrumb {
align-items: baseline;
}
@ -511,6 +519,11 @@ table .group td {
height: 35px;
}
.app-main-content-fields-section.dropdown-container input[type=number] {
width: 210px;
height: 35px;
}
.app-main-content-fields-section.dropdown-container select {
width: 210px;
height: 35px;
@ -1261,6 +1274,175 @@ input[type="checkbox"] {
left:0;
}
.circular-table-container {
position: relative;
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
margin: 30px auto;
}
.table-circle {
position: relative;
text-align: center;
padding-left: 40px;
padding-top: 30px;
}
.table-number {
color: #0066B1;
font-size: 36px;
font-weight: bold;
}
.label-delete-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
border: 1px solid #ccc;
padding: 8px;
border-radius: 8px;
}
.btn-custom-label {
min-width: 225px;
}
.seat-container {
position: absolute;
top: 50%;
left: 50%;
display: flex;
flex-direction: column;
align-items: center;
width: 50px;
}
.seat-circle {
width: 30px;
height: 30px;
background-color: #ccc;
border-radius: 50%;
border: 2px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
text-align: center;
}
.guest-name {
text-align: center;
font-size: 10px;
font-weight: bold;
white-space: nowrap;
color: #333;
}
.seating-chart-container {
width: 100%;
text-align: center;
margin-bottom: 100px;
}
.seating-stage {
width: 100%;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.stage {
color: #0066B1;
font-size: 20px;
font-weight: bold;
background: #eee;
padding: 12px;
width: 100px;
}
.seating-row-container {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
.manage-seating-chart-container {
background: #D0E1F8;
/* right: -250px; */
/* top: 0; */
height: 100vh;
min-width: 300px;
padding: 16px;
overflow: auto;
margin-top: -40px;
margin-left: 40px;
}
.manage-seating-chart-title-container {
font-size: 14px;
color: #555;
display: flex;
justify-content: space-between;
align-items: center;
}
.manage-seating-chart-tables-container {
margin-top: 8px;
margin-bottom: 8px;
}
.manage-seating-chart-tables-container .title {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
position: relative;
}
.seating-page-container {
position: relative;
display: flex;
align-items: flex-start;
}
.table-config-container {
margin-bottom: 4px;
}
.table-config-item-title {
padding: 4px 8px;
display: flex;
justify-content: flex-start;
align-items: center;
font-size: 13px;
}
.table-config-item {
padding: 4px 8px 4px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
position: relative;
}
.table-config-item-title:focus, .table-config-item-title:hover, .table-config-item:focus, .table-config-item:hover {
background: rgb(210, 231, 255);
border-inline-start: 4px solid rgb(28, 125, 249) !important;
}
.seating-popover {
padding:20px;
position: absolute;
background: white;
top: 32px;
left: 0;
z-index: 1000;
border-radius: 8px;
}
@media only screen and (min-width: 1200px) {
/* .container {
max-width: 1200px;

View File

@ -66,6 +66,8 @@ import RouteReportWithSignature from './components/trans-routes/RouteReportWithS
import Layout from "./components/home/layout";
import Home from "./components/home/home";
import Seating from "./components/seating/Seating";
function App() {
const [showMenu, setShowMenu] = useState(false);
@ -146,6 +148,8 @@ function App() {
<Route path="" element={<Navigate replace to="customer-report" />} />
<Route path="customer-report" element={<CustomerReport/>} />
</Route>
<Route path="/seating" element={<Seating />} />
<Route path="/medical" element={<Medical />}>
<Route path="" element={<Navigate replace to="index" />} />

View File

@ -183,7 +183,7 @@ const SideMenu = () => {
},
{
name: 'Seating Chart',
link: '#',
link: '/seating',
roleFunc: () => true
}
]

View File

@ -0,0 +1,45 @@
import React from 'react';
const CircularTable = ({tableNumber, guests = []}) => {
const getPositions = () => {
const positions = [];
const seatCount = 8;
const radius = 60;
for (let i=0; i< seatCount; i++) {
const angle = ((i*360) / seatCount - 90) * (Math.PI / 180);
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
positions.push({x, y, angle})
}
return positions;
}
const seatPositions = getPositions();
// const defaultNames = ['Guest 1', 'Guest 2', 'Guest 3', 'Guest 4', 'Guest 5', 'Guest 6', 'Guest 7', 'Guest 8' ];
const defaultSeatNumber = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
// This is just for test purpose
// const guestNames = [...guests];
// while (guestNames.length < 8) (guestNames.push(defaultNames[guestNames.length]));
return (
<div className="circular-table-container">
<div className="table-circle">
<div className="table-number">{tableNumber}</div>
{seatPositions.map((pos, index) => {
return (<div className="seat-container" key={index} style={{transform: `translate(${pos.x}px, ${pos.y}px)`}}>
<div className="seat-circle" style={guests[index]?.label?.label_color && { background: guests[index]?.label?.label_color}}>{defaultSeatNumber[index]}</div>
<div className="guest-name">{guests[index]?.customerName}</div>
</div>)
})}
</div>
</div>
)
}
export default CircularTable;

View File

@ -0,0 +1,690 @@
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;

View File

@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react";
import { useSelector,useDispatch } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { vehicleSlice, selectVehicleError } from "./../../store";
import { AuthService, VehicleService } from "../../services";
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
import { Archive, Upload } from "react-bootstrap-icons";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import DatePicker from "react-datepicker";
@ -38,7 +38,12 @@ const UpdateVehicle = () => {
const [selectedYearlyFile, setSelectedYearlyFile] = useState();
const [monthlyInspectionDate, setMonthlyInspectionDate] = useState();
const [yearlyInspectionDate, setYearlyInspectionDate] = useState();
const [repairDate, setRepairDate] = useState();
const [repairDescription, setRepairDescription] = useState('');
const [repairPrice, setRepairPrice] = useState('');
const [repairLocation, setRepairLocation] = useState('');
const error = useSelector(selectVehicleError);
const [selectedRepairFile, setSelectedRepairFile] = useState();
useEffect(() => {
if (!AuthService.canAddOrEditVechiles()) {
@ -149,6 +154,22 @@ const UpdateVehicle = () => {
redirectTo();
}
const saveRepair = () => {
const data = {
vehicle: currentVehicle?.id,
repair_date: moment(repairDate).format('MM/DD/YYYY'),
repair_description: repairDescription,
repair_location: repairLocation,
repair_price: repairPrice
}
VehicleRepairService.createNewVehicleRepair(data).then(result => {
const record = result.data;
const formData = new FormData();
formData.append('file', selectedRepairFile);
VehicleService.uploadVechileFile(formData, currentVehicle.id, record.id, 'repair', repairDate).then(() => redirectTo());
})
}
return (
<>
@ -285,7 +306,41 @@ const UpdateVehicle = () => {
</div>
</Tab>
<Tab eventKey="Repair Records" title="Repair Records">
Coming soon...
<h6 className="text-primary">Repair Log</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Repair Date <span className="required">*</span></div>
<DatePicker selected={repairDate} onChange={(v) => setRepairDate(v)} />
</div>
<div className="me-4"><div className="field-label">Cost <span className="required">*</span></div>
<input type="text" value={repairPrice || ''} placeholder="e.g.,$75" onChange={e => setRepairPrice(e.target.value)}/>
</div>
<div className="me-4"><div className="field-label">Repair Location <span className="required">*</span></div>
<input type="text" value={repairLocation || ''} placeholder="e.g.,LocalGarage" onChange={e => setRepairLocation(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Description <span className="required">*</span></div>
<textarea value={repairDescription || ''} onChange={e => setRepairDescription(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Upload Maintenance Files</div>
<label className="custom-file-upload">
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
<input
type="file"
onChange={(e) => setSelectedRepairFile(e.target.files[0])}
/>
</label>
<div className="file-name">{ selectedRepairFile && selectedRepairFile?.name }</div>
</div>
</div>
<div className="col-md-12 col-sm-12 col-xs-12">
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => saveRepair()}> Save </button>
</div>
</Tab>
</Tabs>
<div className="list-func-panel">

View File

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useSelector,useDispatch } from "react-redux";
import { AuthService, VehicleService } from "../../services";
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
import { vehicleSlice, selectVehicleError } from "./../../store";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
@ -14,14 +14,19 @@ const ViewVehicle = () => {
const [currentVehicle, setCurrentVehicle] = useState(undefined);
const [monthlyDocs, setMonthlyDocs] = useState([]);
const [yearlyDocs, setYearlyDocs] = useState([]);
const [repairs, setRepairs] = useState([]);
const [keyword, setKeyword] = useState('');
const [sortingMonthly, setSortingMonthly] = useState({key: '', order: ''});
const [sortingYearly, setSortingYearly] = useState({key: '', order: ''});
const [sortingRepair, setSortingRepair] = useState({key: '', order: ''});
const [selectedItemsMonthly, setSelectedItemsMonthly] = useState([]);
const [selectedItemsYearly, setSelectedItemsYearly] = useState([]);
const [selectedItemsRepair, setSelectedItemsRepair] = useState([]);
const [filteredMonthlyDocs, setFilteredMonthlyDocs] = useState(monthlyDocs);
const [filteredYearlyDocs, setFilteredYearlyDocs] = useState(yearlyDocs);
const [filteredRepairs, setFilteredRepairs] = useState(repairs);
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
const [currentTab, setCurrentTab] = useState('basicInfo')
const redirectTo = () => {
navigate(`/vehicles/list`)
@ -70,6 +75,11 @@ const ViewVehicle = () => {
setMonthlyDocs(monthlyInspectionDocs?.map(item => ({ ...item, inspectionDate: getInspectionDate(item?.name) })));
setYearlyDocs(yearlyInspectionDocs?.map(item => ({ ...item, inspectionDate: getInspectionDate(item?.name) })));
};
const getAllRepairs = async (vid) => {
const v_repairs = (await VehicleRepairService.getAll(vid)).data;
console.log('repairs', v_repairs);
setRepairs(v_repairs);
}
if (!AuthService.canViewVechiles()) {
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
AuthService.logout();
@ -79,17 +89,23 @@ const ViewVehicle = () => {
VehicleService.getVehicle(urlParams.id).then((data) => {
setCurrentVehicle(data.data);
getAllDocuments(data.data?.id, data.data?.vehicle_number);
getAllRepairs(data.data?.id);
})
} else {
getAllDocuments(currentVehicle?.id, currentVehicle?.vehicle_number);
getAllRepairs(currentVehicle?.id);
}
}, []);
useEffect(() => {
setFilteredMonthlyDocs(monthlyDocs.filter(item => item?.name?.toLowerCase().includes(keyword.toLowerCase()) || item?.inspectionDate?.includes(keyword.toLowerCase())));
setFilteredYearlyDocs(yearlyDocs.filter(item => item?.name?.toLowerCase().includes(keyword.toLowerCase()) || item?.inspectionDate?.includes(keyword.toLowerCase())));
setFilteredMonthlyDocs(monthlyDocs?.filter(item => item?.name?.toLowerCase().includes(keyword.toLowerCase()) || item?.inspectionDate?.includes(keyword.toLowerCase())));
setFilteredYearlyDocs(yearlyDocs?.filter(item => item?.name?.toLowerCase().includes(keyword.toLowerCase()) || item?.inspectionDate?.includes(keyword.toLowerCase())));
}, [keyword, yearlyDocs, monthlyDocs]);
useEffect(() => {
setFilteredRepairs(repairs?.filter(item => item?.repair_description?.toLowerCase().includes(keyword.toLowerCase()) || item?.repair_date?.includes(keyword.toLowerCase()) || item?.repair_location?.toLowerCase().includes(keyword.toLowerCase()) || item?.repair_price?.toLowerCase().includes(keyword.toLowerCase()) ));
}, [keyword, repairs])
useEffect(() => {
const newYearlyDocs = [...yearlyDocs];
const sortedYearlyDocs = sortingYearly.key === '' ? newYearlyDocs : newYearlyDocs.sort((a, b) => {
@ -110,6 +126,16 @@ const ViewVehicle = () => {
)
}, [sortingMonthly]);
useEffect(() => {
const newRepairs = [...repairs];
const sortedRepairs = sortingRepair.key === '' ? newRepairs : newRepairs.sort((a, b) => {
return a[sortingRepair.key]?.localeCompare(b[sortingRepair.key]);
});
setRepairs(
sortingRepair.order === 'asc' ? sortedRepairs : sortedRepairs.reverse()
)
}, [sortingRepair]);
const columnsMonthly = [
{
key: 'name',
@ -140,6 +166,29 @@ const ViewVehicle = () => {
}
];
const columnsRepair = [
{
key: 'repair_description',
label: 'Repair Description'
},
{
key: 'repair_date',
label: 'Repair Date'
},
{
key: 'repair_price',
label: 'Cost'
},
{
key: 'repair_location',
label: 'Repair Location'
},
{
key: 'create_date',
label: 'Date Added'
}
];
const sortTableWithFieldMonthly = (key) => {
let newSorting = {
key,
@ -164,6 +213,18 @@ const ViewVehicle = () => {
setSortingYearly(newSorting);
}
const sortTableWithFieldRepair = (key) => {
let newSorting = {
key,
order: 'asc',
}
if (sortingRepair.key === key && sortingRepair.order === 'asc') {
newSorting = {...newSorting, order: 'desc'};
}
setSortingRepair(newSorting);
}
const toggleSelectedAllItemsMonthly = () => {
if (selectedItemsMonthly.length !== filteredMonthlyDocs.length || selectedItemsMonthly.length === 0) {
const newSelectedItems = [...filteredMonthlyDocs].map((doc) => doc.url);
@ -182,6 +243,15 @@ const ViewVehicle = () => {
}
}
const toggleSelectedAllItemsRepair = () => {
if (selectedItemsRepair.length !== filteredRepairs.length || filteredRepairs.length === 0) {
const newSelectedItems = [...filteredRepairs].map((doc) => doc.id);
selectedItemsRepair(newSelectedItems);
} else {
selectedItemsRepair([]);
}
}
const toggleItemYearly = (id) => {
if (selectedItemsYearly.includes(id)) {
const newSelectedItems = [...selectedItemsYearly].filter((item) => item !== id);
@ -202,6 +272,16 @@ const ViewVehicle = () => {
}
}
const toggleItemRepair = (id) => {
if (selectedItemsRepair.includes(id)) {
const newSelectedItems = [...selectedItemsRepair].filter((item) => item !== id);
setSelectedItemsRepair(newSelectedItems);
} else {
const newSelectedItems = [...selectedItemsRepair, id];
setSelectedItemsRepair(newSelectedItems);
}
}
const getSortingImgMonthly = (key) => {
return sortingMonthly.key === key ? (sortingMonthly.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
@ -210,6 +290,10 @@ const ViewVehicle = () => {
return sortingYearly.key === key ? (sortingYearly.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
const getSortingImgRepair = (key) => {
return sortingRepair.key === key ? (sortingRepair.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
const checkSelectAllMonthly = () => {
return selectedItemsMonthly.length === filteredMonthlyDocs.length && selectedItemsMonthly.length > 0;
}
@ -218,6 +302,18 @@ const ViewVehicle = () => {
return selectedItemsYearly.length === filteredYearlyDocs.length && selectedItemsYearly.length > 0;
}
const checkSelectAllRepair = () => {
return selectedItemsRepair.length === selectedItemsRepair.length && selectedItemsRepair.length > 0;
}
const changeTab = (k) => {
setCurrentTab(k);
setKeyword('');
setSortingMonthly({key: '', order: ''});
setSortingYearly({key: '', order: ''});
setSortingRepair({key: '', order: ''});
}
const tableMonthly = <div className="list row mb-4">
<div className="col-md-12">
<table className="personnel-info-table">
@ -279,6 +375,39 @@ const tableYearly = <div className="list row mb-4">
</table>
</div>
</div>;
const tableRepair = <div className="list row mb-4">
<div className="col-md-12">
<table className="personnel-info-table">
<thead>
<tr>
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAllRepair()} onClick={() => toggleSelectedAllItemsRepair()}></input></th>
<th className="th-index">No.</th>
{
columnsRepair.map((column, index) => <th className="sortable-header" key={index}>
{column.label} <span className="float-right" onClick={() => sortTableWithFieldRepair(column.key)}><img src={`/images/${getSortingImgRepair(column.key)}.png`}></img></span>
</th>)
}
</tr>
</thead>
<tbody>
{
filteredRepairs?.map((repair, index) => <tr key={repair.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsRepair.includes(repair?.id)} onClick={()=>toggleItemRepair(repair?.id)}/></td>
<td className="td-index">{index + 1}</td>
<td> {repair?.repair_description}</td>
<td>{repair?.repair_date}</td>
<td>{repair?.repair_price}</td>
<td>{repair?.repair_location}</td>
<td>{repair?.create_date ? new Date(repair?.create_date).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : repair?.repair_date}</td>
</tr>)
}
</tbody>
</table>
</div>
</div>;
return (
<>
@ -298,7 +427,7 @@ const tableYearly = <div className="list row mb-4">
</div>
<div className="app-main-content-list-container">
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="basicInfo" id="customers-tab">
<Tabs defaultActiveKey="basicInfo" id="customers-tab" onSelect={k => changeTab(k)}>
<Tab eventKey="basicInfo" title="Basic Information">
<h6 className="text-primary">Vehicle Information</h6>
<div className="app-main-content-fields-section">
@ -390,13 +519,15 @@ const tableYearly = <div className="list row mb-4">
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
{tableMonthly}
</Tab>
<Tab eventKey="Repair Records" title="Repair Records">
Coming soon...
<Tab eventKey="repairRecords" title="Repair Records">
{tableRepair}
</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" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>
{currentTab !== 'basicInfo' && <input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />}
{currentTab === 'documents' && <button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>}
{currentTab === 'repairRecords' && <button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>}
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
import http from "../http-common";
const getAll = (status) => {
const params = {};
if (status) {
params.status = status;
}
return http.get("/labels", {params});
};
const createNewLabel = (data) => {
data.status = 'active';
return http.post('/labels', data);
};
const updateLabel = (id, data) => {
return http.put(`/labels/${id}`, data);
}
const deleteLabel = (id) => {
return http.delete(`/labels/${id}`)
}
export const LabelService = {
getAll,
createNewLabel,
updateLabel,
deleteLabel
}

View File

@ -0,0 +1,28 @@
import http from "../http-common";
const getAll = (date) => {
const params = {};
if (date) {
params.date = date;
}
return http.get("/seatings", {params});
};
const createNewSeating = (data) => {
data.status = 'active';
return http.post('/seatings', data);
};
const updateSeating = (id, data) => {
return http.put(`/seatings/${id}`, data);
}
const deleteSeating = (id) => {
return http.delete(`/seatings/${id}`)
}
export const SeatingService = {
getAll,
createNewSeating,
updateSeating,
deleteSeating
}

View File

@ -0,0 +1,12 @@
import http from "../http-common";
const getAll = (vehicle) => {
return http.get(`/vehicle-repairs?vehicle=${vehicle}`);
};
const createNewVehicleRepair = (data) => {
return http.post('/vehicle-repairs', data);
};
export const VehicleRepairService = {
getAll,
createNewVehicleRepair
};

View File

@ -13,4 +13,7 @@ export * from './CenterPhoneService';
export * from './ResourceService';
export * from './EventsService';
export * from './EventRequestService';
export * from './SignatureRequestService';
export * from './SignatureRequestService';
export * from './VehicleRepairService';
export * from './LabelService';
export * from './SeatingService';

View File

@ -189,6 +189,9 @@ app.get('/medical/resources/:id', function (req,res) {
app.get('/signature/:id', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('/seating', function (req,res) {
res.sendFile(path + "index.html");
});
require("./app/routes/user.routes")(app);
require("./app/routes/auth.routes")(app);
require("./app/routes/employee.routes")(app);
@ -213,6 +216,9 @@ require("./app/routes/event-request.routes")(app);
require("./app/routes/signature-request.routes")(app);
require("./app/routes/snack.routes")(app);
require("./app/routes/lunch.routes")(app);
require("./app/routes/vehicle-repair.route")(app);
require("./app/routes/seating.routes")(app);
require("./app/routes/label.routes")(app);
// set port, listen for requests
const PORT = process.env.PORT || 8080;