Fix
This commit is contained in:
parent
c1991211aa
commit
36a16fa658
119
app/controllers/daily-routes-template.controller.js
Normal file
119
app/controllers/daily-routes-template.controller.js
Normal file
@ -0,0 +1,119 @@
|
||||
const { splitSite } = require("../middlewares");
|
||||
const db = require("../models");
|
||||
const DailyRoutesTemplate = db.dailyRoutesTemplate;
|
||||
|
||||
// Create and Save a new Daily Routes Template
|
||||
exports.createDailyRoutesTemplate = (req, res) => {
|
||||
// Validate request
|
||||
if (!req.body.name) {
|
||||
res.status(400).send({ message: "Content can not be empty!" });
|
||||
return;
|
||||
}
|
||||
const site = splitSite.findSiteNumber(req);
|
||||
// Create a Daily Routes Template
|
||||
const dailyRoutesTemplate = new DailyRoutesTemplate({
|
||||
name: req.body.name,
|
||||
template_date: req.body.template_date,
|
||||
routes: req.body.routes || [],
|
||||
site,
|
||||
create_by: req.body.create_by || '',
|
||||
create_date: new Date()
|
||||
});
|
||||
// Save Daily Routes Template in the database
|
||||
dailyRoutesTemplate
|
||||
.save(dailyRoutesTemplate)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while creating the Daily Routes Template."
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Retrieve all Daily Routes Templates from the database
|
||||
exports.getAllDailyRoutesTemplates = (req, res) => {
|
||||
var params = req.query;
|
||||
var condition = {};
|
||||
condition = splitSite.splitSiteGet(req, condition);
|
||||
|
||||
if (params.template_date) {
|
||||
condition.template_date = params.template_date;
|
||||
}
|
||||
|
||||
DailyRoutesTemplate.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while retrieving Daily Routes Templates."
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get One Daily Routes Template by Id
|
||||
exports.getDailyRoutesTemplate = (req, res) => {
|
||||
const id = req.params.id;
|
||||
DailyRoutesTemplate.findById(id)
|
||||
.then(data => {
|
||||
if (!data)
|
||||
res.status(404).send({ message: "Not found Daily Routes Template with id " + id });
|
||||
else res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res
|
||||
.status(500)
|
||||
.send({ message: "Error retrieving Daily Routes Template with id=" + id });
|
||||
});
|
||||
};
|
||||
|
||||
// Update a Daily Routes Template by the id in the request
|
||||
exports.updateDailyRoutesTemplate = (req, res) => {
|
||||
if (!req.body) {
|
||||
return res.status(400).send({
|
||||
message: "Data to update can not be empty!"
|
||||
});
|
||||
}
|
||||
const id = req.params.id;
|
||||
DailyRoutesTemplate.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
|
||||
.then(data => {
|
||||
if (!data) {
|
||||
res.status(404).send({
|
||||
message: `Cannot update Daily Routes Template with id=${id}. Maybe Daily Routes Template was not found!`
|
||||
});
|
||||
} else res.send({ success: true, message: "Daily Routes Template was updated successfully." });
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
success: false,
|
||||
message: "Error updating Daily Routes Template with id=" + id
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Delete a Daily Routes Template by id
|
||||
exports.deleteDailyRoutesTemplate = (req, res) => {
|
||||
const id = req.params.id;
|
||||
DailyRoutesTemplate.findByIdAndRemove(id)
|
||||
.then(data => {
|
||||
if (!data) {
|
||||
res.status(404).send({
|
||||
message: `Cannot delete Daily Routes Template with id=${id}. Maybe Daily Routes Template was not found!`
|
||||
});
|
||||
} else {
|
||||
res.send({
|
||||
message: "Daily Routes Template was deleted successfully!"
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message: "Could not delete Daily Routes Template with id=" + id
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
198
app/controllers/fingerprint-attendance.controller.js
Normal file
198
app/controllers/fingerprint-attendance.controller.js
Normal file
@ -0,0 +1,198 @@
|
||||
const db = require("../models");
|
||||
const FingerprintAttendance = db.fingerprint_attendance;
|
||||
const { splitSite } = require("../middlewares");
|
||||
|
||||
// Create and Save a new Fingerprint Attendance record
|
||||
exports.createFingerprintAttendance = (req, res) => {
|
||||
// Validate request
|
||||
if (!req.body.employee) {
|
||||
res.status(400).send({ message: "Employee is required!" });
|
||||
return;
|
||||
}
|
||||
|
||||
const site = splitSite.findSiteNumber(req);
|
||||
// Create a Fingerprint Attendance record
|
||||
const fingerprintAttendance = new FingerprintAttendance({
|
||||
employee: req.body.employee,
|
||||
finger_print: req.body.finger_print || '',
|
||||
finger_print_time: req.body.finger_print_time || new Date(),
|
||||
create_date: req.body.create_date || new Date(),
|
||||
create_by: req.body.create_by || '',
|
||||
update_date: req.body.update_date || new Date(),
|
||||
update_by: req.body.update_by || '',
|
||||
note: req.body.note || '',
|
||||
site: req.body.site
|
||||
});
|
||||
|
||||
// Save Fingerprint Attendance in the database
|
||||
fingerprintAttendance
|
||||
.save(fingerprintAttendance)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while creating the Fingerprint Attendance record."
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Retrieve all Fingerprint Attendance records from the database
|
||||
exports.getAllFingerprintAttendances = (req, res) => {
|
||||
var params = req.query;
|
||||
var condition = {};
|
||||
condition = splitSite.splitSiteGet(req, condition);
|
||||
|
||||
// Add filters based on query parameters
|
||||
if (params.employee) {
|
||||
condition.employee = params.employee;
|
||||
}
|
||||
if (params.finger_print) {
|
||||
condition.finger_print = params.finger_print;
|
||||
}
|
||||
if (params.create_by) {
|
||||
condition.create_by = params.create_by;
|
||||
}
|
||||
if (params.site) {
|
||||
condition.site = params.site;
|
||||
}
|
||||
|
||||
FingerprintAttendance.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while retrieving fingerprint attendance records."
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get One Fingerprint Attendance record by Id
|
||||
exports.getFingerprintAttendance = (req, res) => {
|
||||
const id = req.params.id;
|
||||
FingerprintAttendance.findById(id)
|
||||
.then(data => {
|
||||
if (!data)
|
||||
res.status(404).send({ message: "Not found Fingerprint Attendance record with id " + id });
|
||||
else res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res
|
||||
.status(500)
|
||||
.send({ message: "Error retrieving Fingerprint Attendance record with id=" + id });
|
||||
});
|
||||
};
|
||||
|
||||
// Update a Fingerprint Attendance record by the id in the request
|
||||
exports.updateFingerprintAttendance = (req, res) => {
|
||||
if (!req.body) {
|
||||
return res.status(400).send({
|
||||
message: "Data to update can not be empty!"
|
||||
});
|
||||
}
|
||||
const id = req.params.id;
|
||||
|
||||
// Add update_date and update_by to the request body
|
||||
req.body.update_date = new Date();
|
||||
|
||||
FingerprintAttendance.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
|
||||
.then(data => {
|
||||
if (!data) {
|
||||
res.status(404).send({
|
||||
message: `Cannot update fingerprint attendance record with id=${id}. Maybe Fingerprint Attendance record was not found!`
|
||||
});
|
||||
} else res.send({ success: true, message: "Fingerprint Attendance record was updated successfully." });
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
success: false,
|
||||
message: "Error updating Fingerprint Attendance record with id=" + id
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Delete a Fingerprint Attendance record by id
|
||||
exports.deleteFingerprintAttendance = (req, res) => {
|
||||
const id = req.params.id;
|
||||
FingerprintAttendance.findByIdAndRemove(id)
|
||||
.then(data => {
|
||||
if (!data) {
|
||||
res.status(404).send({
|
||||
message: `Cannot delete fingerprint attendance record with id=${id}. Maybe Fingerprint Attendance record was not found!`
|
||||
});
|
||||
} else {
|
||||
res.send({
|
||||
message: "Fingerprint Attendance record was deleted successfully!"
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message: "Could not delete Fingerprint Attendance record with id=" + id
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get Fingerprint Attendance records by employee
|
||||
exports.getFingerprintAttendancesByEmployee = (req, res) => {
|
||||
const employee = req.params.employee;
|
||||
var params = req.query;
|
||||
var condition = { employee: employee };
|
||||
condition = splitSite.splitSiteGet(req, condition);
|
||||
|
||||
// Add additional filters
|
||||
if (params.start_date && params.end_date) {
|
||||
condition.finger_print_time = { $gte: new Date(params.start_date), $lte: new Date(params.end_date) };
|
||||
}
|
||||
if (params.site) {
|
||||
condition.site = params.site;
|
||||
}
|
||||
|
||||
FingerprintAttendance.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while retrieving fingerprint attendance records for employee " + employee
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get Fingerprint Attendance records by date range
|
||||
exports.getFingerprintAttendancesByDateRange = (req, res) => {
|
||||
var params = req.query;
|
||||
|
||||
if (!params.start_date || !params.end_date) {
|
||||
res.status(400).send({ message: "Start date and end date are required!" });
|
||||
return;
|
||||
}
|
||||
|
||||
var condition = {
|
||||
finger_print_time: { $gte: new Date(params.start_date), $lte: new Date(params.end_date) }
|
||||
};
|
||||
condition = splitSite.splitSiteGet(req, condition);
|
||||
|
||||
// Add additional filters
|
||||
if (params.employee) {
|
||||
condition.employee = params.employee;
|
||||
}
|
||||
if (params.site) {
|
||||
condition.site = params.site;
|
||||
}
|
||||
|
||||
FingerprintAttendance.find(condition)
|
||||
.then(data => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message:
|
||||
err.message || "Some error occurred while retrieving fingerprint attendance records for date range."
|
||||
});
|
||||
});
|
||||
};
|
||||
80
app/models/daily-routes-template.model.js
Normal file
80
app/models/daily-routes-template.model.js
Normal file
@ -0,0 +1,80 @@
|
||||
const uniqueValidator = require('mongoose-unique-validator');
|
||||
module.exports = mongoose => {
|
||||
// Import the route customer list schema from route-path
|
||||
var routeCustomerListSchema = mongoose.Schema({
|
||||
customer_id: String,
|
||||
customer_name: String,
|
||||
customer_address: String,
|
||||
customer_avatar: String,
|
||||
customer_group: String,
|
||||
customer_group_address: String,
|
||||
customer_type: String,
|
||||
customer_pickup_status: String,
|
||||
customer_note: String,
|
||||
customer_special_needs: String,
|
||||
customer_phone: String,
|
||||
customer_enter_center_time: Date,
|
||||
customer_leave_center_time: Date,
|
||||
customer_pickup_time: Date,
|
||||
customer_dropoff_time: Date,
|
||||
customer_route_status: String,
|
||||
customer_pickup_order: Number,
|
||||
customer_table_id: String,
|
||||
customer_transfer_to_route: String,
|
||||
customer_language: String,
|
||||
customer_estimated_pickup_time: String,
|
||||
customer_estimated_dropoff_time: String,
|
||||
customer_address_override: String,
|
||||
});
|
||||
|
||||
var checklistResultSchema = mongoose.Schema({
|
||||
item: String,
|
||||
result: Boolean
|
||||
});
|
||||
|
||||
// Route path schema to store in the template
|
||||
var routePathSchema = mongoose.Schema({
|
||||
name: String,
|
||||
schedule_date: String,
|
||||
vehicle: String,
|
||||
status: [{
|
||||
type: String
|
||||
}],
|
||||
driver: String,
|
||||
type: String,
|
||||
start_mileage: Number,
|
||||
end_mileage: Number,
|
||||
start_time: Date,
|
||||
end_time: Date,
|
||||
estimated_start_time: Date,
|
||||
route_customer_list: [{
|
||||
type: routeCustomerListSchema
|
||||
}],
|
||||
checklist_result: [{
|
||||
type: checklistResultSchema,
|
||||
}],
|
||||
});
|
||||
|
||||
var schema = mongoose.Schema(
|
||||
{
|
||||
name: String,
|
||||
template_date: String,
|
||||
routes: [{
|
||||
type: routePathSchema
|
||||
}],
|
||||
site: Number,
|
||||
create_by: String,
|
||||
create_date: Date
|
||||
},
|
||||
{ collection: 'daily_routes_template', timestamps: true }
|
||||
);
|
||||
schema.method("toJSON", function() {
|
||||
const { __v, _id, ...object } = this.toObject();
|
||||
object.id = _id;
|
||||
return object;
|
||||
});
|
||||
schema.plugin(uniqueValidator);
|
||||
const DailyRoutesTemplate = mongoose.model("daily_routes_template", schema);
|
||||
return DailyRoutesTemplate;
|
||||
};
|
||||
|
||||
23
app/models/fingerprint-attendance.model.js
Normal file
23
app/models/fingerprint-attendance.model.js
Normal file
@ -0,0 +1,23 @@
|
||||
module.exports = (mongoose) => {
|
||||
var schema = mongoose.Schema(
|
||||
{
|
||||
employee: String,
|
||||
finger_print: String,
|
||||
finger_print_time: Date,
|
||||
create_date: Date,
|
||||
create_by: String,
|
||||
update_date: Date,
|
||||
update_by: String,
|
||||
note: String,
|
||||
site: Number
|
||||
},
|
||||
{ collection: 'fingerprint_attendance', timestamps: true }
|
||||
);
|
||||
schema.method("toJSON", function() {
|
||||
const { __v, _id, ...object } = this.toObject();
|
||||
object.id = _id;
|
||||
return object;
|
||||
});
|
||||
const FingerprintAttendance = mongoose.model("fingerprint_attendance", schema);
|
||||
return FingerprintAttendance;
|
||||
};
|
||||
24
app/routes/daily-routes-template.routes.js
Normal file
24
app/routes/daily-routes-template.routes.js
Normal file
@ -0,0 +1,24 @@
|
||||
const {authJwt} = require("../middlewares");
|
||||
module.exports = app => {
|
||||
const dailyRoutesTemplates = require("../controllers/daily-routes-template.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 daily routes templates
|
||||
router.get("/", [authJwt.verifyToken], dailyRoutesTemplates.getAllDailyRoutesTemplates);
|
||||
// Get one daily routes template by id
|
||||
router.get("/:id", [authJwt.verifyToken], dailyRoutesTemplates.getDailyRoutesTemplate);
|
||||
// Create a new daily routes template
|
||||
router.post("/", [authJwt.verifyToken], dailyRoutesTemplates.createDailyRoutesTemplate);
|
||||
// Update a daily routes template with id
|
||||
router.put("/:id", [authJwt.verifyToken], dailyRoutesTemplates.updateDailyRoutesTemplate);
|
||||
// Delete a daily routes template with id
|
||||
router.delete("/:id", [authJwt.verifyToken], dailyRoutesTemplates.deleteDailyRoutesTemplate);
|
||||
app.use('/api/daily-routes-templates', router);
|
||||
};
|
||||
|
||||
35
app/routes/fingerprint-attendance.routes.js
Normal file
35
app/routes/fingerprint-attendance.routes.js
Normal file
@ -0,0 +1,35 @@
|
||||
const {authJwt} = require("../middlewares");
|
||||
module.exports = app => {
|
||||
const fingerprintAttendance = require("../controllers/fingerprint-attendance.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();
|
||||
|
||||
// Create a new Fingerprint Attendance record
|
||||
router.post("/", fingerprintAttendance.createFingerprintAttendance);
|
||||
|
||||
// Retrieve all Fingerprint Attendance records
|
||||
router.get("/", [authJwt.verifyToken], fingerprintAttendance.getAllFingerprintAttendances);
|
||||
|
||||
// Get Fingerprint Attendance records by date range (MUST come before /:id route)
|
||||
router.get("/date-range", [authJwt.verifyToken], fingerprintAttendance.getFingerprintAttendancesByDateRange);
|
||||
|
||||
// Get Fingerprint Attendance records by employee (MUST come before /:id route)
|
||||
router.get("/employee/:employee", [authJwt.verifyToken], fingerprintAttendance.getFingerprintAttendancesByEmployee);
|
||||
|
||||
// Retrieve a single Fingerprint Attendance record with id
|
||||
router.get("/:id", [authJwt.verifyToken], fingerprintAttendance.getFingerprintAttendance);
|
||||
|
||||
// Update a Fingerprint Attendance record with id
|
||||
router.put("/:id", [authJwt.verifyToken], fingerprintAttendance.updateFingerprintAttendance);
|
||||
|
||||
// Delete a Fingerprint Attendance record with id
|
||||
router.delete("/:id", [authJwt.verifyToken], fingerprintAttendance.deleteFingerprintAttendance);
|
||||
|
||||
app.use('/api/fingerprint-attendance', router);
|
||||
};
|
||||
7
app/views/static/css/main.7fa0ef40.css
Normal file
7
app/views/static/css/main.7fa0ef40.css
Normal file
File diff suppressed because one or more lines are too long
1
app/views/static/css/main.7fa0ef40.css.map
Normal file
1
app/views/static/css/main.7fa0ef40.css.map
Normal file
File diff suppressed because one or more lines are too long
3
app/views/static/js/main.ea61a51a.js
Normal file
3
app/views/static/js/main.ea61a51a.js
Normal file
File diff suppressed because one or more lines are too long
140
app/views/static/js/main.ea61a51a.js.LICENSE.txt
Normal file
140
app/views/static/js/main.ea61a51a.js.LICENSE.txt
Normal file
@ -0,0 +1,140 @@
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*!
|
||||
* @kurkle/color v0.3.4
|
||||
* https://github.com/kurkle/color#readme
|
||||
* (c) 2024 Jukka Kurkela
|
||||
* Released under the MIT License
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Chart.js v4.5.0
|
||||
* https://www.chartjs.org
|
||||
* (c) 2025 Chart.js Contributors
|
||||
* Released under the MIT License
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Signature Pad v2.3.2
|
||||
* https://github.com/szimek/signature_pad
|
||||
*
|
||||
* Copyright 2017 Szymon Nowak
|
||||
* Released under the MIT license
|
||||
*
|
||||
* The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
|
||||
* http://corner.squareup.com/2012/07/smoother-signatures.html
|
||||
*
|
||||
* Implementation of interpolation using cubic Bézier curves is taken from:
|
||||
* http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
|
||||
*
|
||||
* Algorithm for approximated length of a Bézier curve is taken from:
|
||||
* http://www.lemoda.net/maths/bezier-length/index.html
|
||||
*
|
||||
*/
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.3.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
//! moment.js
|
||||
1
app/views/static/js/main.ea61a51a.js.map
Normal file
1
app/views/static/js/main.ea61a51a.js.map
Normal file
File diff suppressed because one or more lines are too long
751
client/src/components/center-calendar/CenterCalendar.js.backup
Normal file
751
client/src/components/center-calendar/CenterCalendar.js.backup
Normal file
@ -0,0 +1,751 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthService, EventsService, CustomerService, ResourceService, VehicleService, EmployeeService } from "../../services";
|
||||
import moment from 'moment';
|
||||
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown } from "react-bootstrap";
|
||||
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
|
||||
import {
|
||||
viewMonthGrid,
|
||||
createViewDay,
|
||||
createViewWeek,
|
||||
createViewMonthGrid
|
||||
} from '@schedule-x/calendar';
|
||||
import { createEventsServicePlugin } from '@schedule-x/events-service';
|
||||
import { createEventModalPlugin} from '@schedule-x/event-modal';
|
||||
import { createEventRecurrencePlugin } from "@schedule-x/event-recurrence";
|
||||
import '@schedule-x/theme-default/dist/calendar.css';
|
||||
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { vehicleSlice } from "../../store";
|
||||
// import { Scheduler } from "@aldabil/react-scheduler";
|
||||
|
||||
|
||||
const EventsCalendar = () => {
|
||||
const navigate = useNavigate();
|
||||
const [events, setEvents] = useState([]);
|
||||
const [allEvents, setAllEvents] = useState([]);
|
||||
const [targetedEventType, setTargetedEventType] = useState('medical');
|
||||
const [currentTab, setCurrentTab] = useState('medicalCalendar');
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [resources, setResources] = useState([]);
|
||||
const [fromDate, setFromDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
||||
const [toDate, setToDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0));
|
||||
const [currentTotalTranslate1, setCurrentTotalTranslate1] = useState(0);
|
||||
const [currentTotalTranslate2, setCurrentTotalTranslate2] = useState(0);
|
||||
const [currentTotalResource, setCurrentTotalResource] = useState(0);
|
||||
const [showDeletedItems, setShowDeletedItems] = useState(false);
|
||||
const [timeData, setTimeData] = useState([]);
|
||||
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
||||
const eventsServicePlugin = createEventsServicePlugin();
|
||||
const eventModalService = createEventModalPlugin();
|
||||
const eventRecurrence = createEventRecurrencePlugin();
|
||||
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
||||
const [currentView, setCurrentView] = useState('month');
|
||||
const [currentViewDate, setCurrentViewDate] = useState(new Date());
|
||||
const [showCreationModal, setShowCreationModal] = useState(false);
|
||||
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
|
||||
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date())
|
||||
const [newEventType, setNewEventType] = useState('');
|
||||
const [newEventTitle, setNewEventTitle] = useState('');
|
||||
const [newEventDescription, setNewEventDescription] = useState('');
|
||||
const [newEventLocation, setNewEventLocation] = useState('');
|
||||
const [newEventTarget, setNewEventTarget] = useState(undefined);
|
||||
const [newEventSource, setNewEventSource] = useState(undefined);
|
||||
const [newEventDepartment, setNewEventDepartment] = useState('');
|
||||
const [newEventColor, setNewEventColor] = useState('');
|
||||
const [newEventSourceType, setNewEventSourceType] = useState('');
|
||||
const [newEventTargetType, setNewEventTargetType] = useState('');
|
||||
const [newEventFutureDate, setNewEventFutureDate] = useState(undefined);
|
||||
const [newEventReminderType, setNewEventReminderType] = useState('');
|
||||
const [newEventRecurring, setNewEventRecurring] = useState(undefined);
|
||||
const [vehicles, setVehicles] = useState([]);
|
||||
const [employees, setEmployees] = useState([]);
|
||||
|
||||
// Helper function to format name from "lastname, firstname" to "firstname lastname"
|
||||
const formatFullName = (name) => {
|
||||
if (!name) return '';
|
||||
if (name.includes(',')) {
|
||||
const parts = name.split(',').map(part => part.trim());
|
||||
return `${parts[1]} ${parts[0]}`; // firstname lastname
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
// Helper function to get shortened name
|
||||
const getShortenedName = (name) => {
|
||||
if (!name) return '';
|
||||
const fullName = formatFullName(name);
|
||||
const parts = fullName.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
return `${parts[0]} ${parts[parts.length - 1].charAt(0)}`; // FirstName L
|
||||
}
|
||||
return fullName;
|
||||
};
|
||||
|
||||
// Helper function to format event title
|
||||
const formatEventTitle = (customerName, startTime) => {
|
||||
const fullName = formatFullName(customerName);
|
||||
const timeStr = startTime ? moment(new Date(startTime)).format('hh:mm A') : '';
|
||||
|
||||
// Try: "Time: Full Name"
|
||||
const titleWithTime = timeStr ? `${timeStr}: ${fullName}` : fullName;
|
||||
if (titleWithTime.length <= 35) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
// Try: "Full Name"
|
||||
if (fullName.length <= 35) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
// Use: "FirstName L"
|
||||
return getShortenedName(customerName);
|
||||
};
|
||||
|
||||
const eventTypeMap = {
|
||||
medicalCalendar: 'medical',
|
||||
activitiesCalendar: 'activity',
|
||||
incidentsCalendar: 'incident',
|
||||
mealPlanCalendar: 'meal_plan',
|
||||
reminderDatesCalendar: 'reminder'
|
||||
}
|
||||
|
||||
const calendar = useCalendarApp({
|
||||
views: [createViewMonthGrid(), createViewDay(), createViewWeek()],
|
||||
monthGridOptions: {
|
||||
/**
|
||||
* Number of events to display in a day cell before the "+ N events" button is shown
|
||||
* */
|
||||
nEventsPerDay: 50,
|
||||
},
|
||||
defaultView: viewMonthGrid.name,
|
||||
skipValidation: true,
|
||||
selectedDate: moment(new Date()).format('YYYY-MM-DD HH:mm'),
|
||||
events: events,
|
||||
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
|
||||
callbacks: {
|
||||
onSelectedDateUpdate(date) {
|
||||
setCurrentViewDate(new Date(date));
|
||||
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
|
||||
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
|
||||
},
|
||||
onViewChange(view) {
|
||||
setCurrentView(view);
|
||||
},
|
||||
onClickDate(date) {
|
||||
setNewEventStartDateTime(new Date(date))
|
||||
setNewEventEndDateTime(new Date(date));
|
||||
setShowCreationModal(true);
|
||||
},
|
||||
onClickDateTime(dateTime) {
|
||||
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
|
||||
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
|
||||
setShowCreationModal(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Filter events based on current view
|
||||
const getFilteredEvents = () => {
|
||||
const viewDate = moment(currentViewDate);
|
||||
|
||||
// Check for day view
|
||||
if (currentView && currentView.toLowerCase().includes('day')) {
|
||||
// Show only events for the selected day
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isSame(viewDate, 'day');
|
||||
});
|
||||
}
|
||||
// Check for week view
|
||||
else if (currentView && currentView.toLowerCase().includes('week')) {
|
||||
// Show events for the week containing the selected date
|
||||
const startOfWeek = viewDate.clone().startOf('week');
|
||||
const endOfWeek = viewDate.clone().endOf('week');
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isBetween(startOfWeek, endOfWeek, 'day', '[]');
|
||||
});
|
||||
}
|
||||
// Month view (including month-grid and month-agenda)
|
||||
else {
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isSame(viewDate, 'month');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getGroupedEvents = () => {
|
||||
const eventsDateMap = new Map();
|
||||
const filteredEvents = getFilteredEvents();
|
||||
for (const eventItem of filteredEvents) {
|
||||
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||
if (eventsDateMap.has(dateString)) {
|
||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||
} else {
|
||||
const value = [];
|
||||
value.push(eventItem);
|
||||
eventsDateMap.set(dateString, value);
|
||||
}
|
||||
}
|
||||
return eventsDateMap;
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
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();
|
||||
navigate(`/login`);
|
||||
}
|
||||
VehicleService.getAllActiveVehicles().then((data) => {
|
||||
setVehicles(data.data)
|
||||
});
|
||||
EmployeeService.getAllEmployees().then((data) => {
|
||||
setEmployees(data.data);
|
||||
});
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
setCustomers(data.data);
|
||||
});
|
||||
ResourceService.getAll().then((data) => {
|
||||
setResources(data.data);
|
||||
});
|
||||
EventsService.getTimeData().then(data => {
|
||||
setTimeData(data.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
|
||||
}, [fromDate, toDate]);
|
||||
|
||||
useEffect(() => {
|
||||
setNewEventType(eventTypeMap[currentTab]);
|
||||
if (currentTab === 'medicalCalendar') {
|
||||
if (customers?.length > 0 && resources.length > 0) {
|
||||
const orignialEvents = [...allEvents];
|
||||
setEvents(orignialEvents?.filter(item => item.type === 'medical')?.map((item) => {
|
||||
const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
|
||||
const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
|
||||
item.event_id = item.id;
|
||||
item.customer = customerField;
|
||||
item.doctor = doctorField;
|
||||
item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
|
||||
item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
|
||||
item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
|
||||
item.translation = item?.data?.interpreter || '';
|
||||
item.newPatient = item?.data?.new_patient || '';
|
||||
item.needId = item?.data?.need_id || '';
|
||||
item.disability = item?.data?.disability || '';
|
||||
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
||||
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
||||
item.fasting = item?.data?.fasting || '';
|
||||
item.transportation = item?.link_event_name || '';
|
||||
item.title = currentTab==='medicalCalendar' ? formatEventTitle(customerField, item?.start_time) : item.title;
|
||||
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
|
||||
item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||
const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData);
|
||||
const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
|
||||
item.color = item?.color;
|
||||
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
||||
item.showWarnings = isFutureEvent;
|
||||
item.maxTranslate1 = maxTranslate1;
|
||||
item.maxTranslate2 = maxTranslate2;
|
||||
item.maxResource = maxResource;
|
||||
item.totalTranslate1 = totalTranslate1;
|
||||
setCurrentTotalTranslate1(item.totalTranslate1);
|
||||
item.totalTranslate2 = totalTranslate2;
|
||||
setCurrentTotalTranslate2(item.totalTranslate2);
|
||||
item.totalResource = totalResource;
|
||||
setCurrentTotalResource(item.totalResource);
|
||||
return item;
|
||||
})?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
|
||||
}
|
||||
} else {
|
||||
const originalEvents = [...allEvents];
|
||||
setEvents(originalEvents?.filter(item => item.type === eventTypeMap[currentTab])?.map(item => ({
|
||||
...item,
|
||||
title: item?.title,
|
||||
start: item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`,
|
||||
end: item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`),
|
||||
_options: { additionalClasses: [`event-${item?.color || 'primary'}`]}
|
||||
}))?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
|
||||
}
|
||||
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
|
||||
|
||||
useEffect(() => {
|
||||
if (events && calendar) {
|
||||
calendar?.eventsService?.set(events);
|
||||
setGroupedEvents(getGroupedEvents());
|
||||
}
|
||||
}, [events, currentView, currentViewDate]);
|
||||
|
||||
|
||||
const redirectToAdmin = () => {
|
||||
navigate(`/medical`)
|
||||
}
|
||||
|
||||
const goToEdit = (id) => {
|
||||
navigate(`/medical/events/edit/${id}?from=calendar`)
|
||||
}
|
||||
|
||||
const goToCreateNew = () => {
|
||||
navigate(`/medical/events`)
|
||||
}
|
||||
|
||||
const goToList = () => {
|
||||
navigate(`/medical/events/list`)
|
||||
}
|
||||
|
||||
const goToMultipleList = () => {
|
||||
navigate(`/medical/events/multiple-list`)
|
||||
}
|
||||
|
||||
|
||||
const goToView = (id) => {
|
||||
navigate(`/medical/events/${id}`)
|
||||
}
|
||||
|
||||
const disableEvent = (id) => {
|
||||
const currentEvent = events.find(item => item.id === id);
|
||||
EventsService.disableEvent(id, { status: 'inactive', edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
edit_history: currentEvent?.edit_history? [...currentEvent.edit_history, { employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] : [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]}).then(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
||||
setAllEvents(data?.data);
|
||||
// if (currentTab === 'medicalCalendar') {
|
||||
// setEvents(data.data.filter((item) => {
|
||||
// const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
|
||||
// const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
|
||||
// item.event_id = item.id;
|
||||
// item.customer = customerField;
|
||||
// item.doctor = doctorField;
|
||||
// item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
|
||||
// item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
|
||||
// item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
|
||||
// item.translation = item?.data?.interpreter || '';
|
||||
// item.newPatient = item?.data?.new_patient || '';
|
||||
// item.needId = item?.data?.need_id || '';
|
||||
// item.disability = item?.data?.disability || '';
|
||||
// item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}` ;
|
||||
// item.endTime = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||
// item.fasting = item?.data?.fasting || '';
|
||||
// item.transportation = item?.link_event_name || '';
|
||||
// item.title = `${customerField}, provider: ${doctorField}`;
|
||||
// item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
|
||||
// item.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||
// item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
||||
// const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
|
||||
// const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
|
||||
// item.color = item?.color;
|
||||
// item.showWarnings = isFutureEvent;
|
||||
// item.maxTranslate1 = maxTranslate1;
|
||||
// item.maxTranslate2 = maxTranslate2;
|
||||
// item.maxResource = maxResource;
|
||||
// item.totalTranslate1 = totalTranslate1;
|
||||
// setCurrentTotalTranslate1(item.totalTranslate1);
|
||||
// item.totalTranslate2 = totalTranslate2;
|
||||
// setCurrentTotalTranslate2(item.totalTranslate2);
|
||||
// item.totalResource = totalResource;
|
||||
// setCurrentTotalResource(item.totalResource);
|
||||
// return item;
|
||||
// }).filter(item => item.type === 'medical'));
|
||||
// }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const FilterAndClose = () => {
|
||||
setShowFilterDropdown(false);
|
||||
}
|
||||
|
||||
const cleanFilterAndClose = () => {
|
||||
setShowFilterDropdown(false);
|
||||
setShowDeletedItems(false);
|
||||
}
|
||||
|
||||
const goToTab = (value) => {
|
||||
setTargetedEventType(eventTypeMap[value]);
|
||||
setCurrentTab(value);
|
||||
}
|
||||
|
||||
|
||||
const customComponents = {
|
||||
eventModal: ({calendarEvent}) => {
|
||||
return <>
|
||||
|
||||
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</div>
|
||||
{ calendarEvent?.doctor && <div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</div>}
|
||||
<div className="sx__event-modal__time">{`${calendarEvent?.start}`}</div>
|
||||
<div className="sx__event-modal__time">
|
||||
{currentTab === 'medicalCalendar' && <PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>}
|
||||
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </div>
|
||||
</>
|
||||
}
|
||||
};
|
||||
|
||||
const customMenu = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={className}
|
||||
aria-labelledby={labeledBy}
|
||||
>
|
||||
<h6>Filter By</h6>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Show Deleted Events</div>
|
||||
<input type="checkbox" value={showDeletedItems} checked={showDeletedItems === true} onClick={() => setShowDeletedItems(!showDeletedItems)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => cleanFilterAndClose()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const calendarView = <div className="multi-columns-container">
|
||||
<div className="column-container" style={{'minWidth': '1000px'}}>
|
||||
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
||||
</div>
|
||||
<div className="column-container">
|
||||
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden' }}>
|
||||
<h6 className="text-primary me-4">List</h6>
|
||||
{
|
||||
Array.from(groupedEvents?.keys())?.map((key) => {
|
||||
return <div key={key}>
|
||||
<h6 className="text-primary me-2">{key}</h6>
|
||||
{
|
||||
groupedEvents.get(key).map(eventItem => <div
|
||||
key={eventItem.id}
|
||||
className={`event-${eventItem.color || 'primary'} mb-4 event-list-item-container`}
|
||||
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
|
||||
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default' }}
|
||||
>
|
||||
<div className="event-item-flex">
|
||||
<div className="sx__month-agenda-event__title">
|
||||
{currentTab === 'medicalCalendar'
|
||||
? `${moment(eventItem?.start_time).format('hh:mm A')}: ${formatFullName(eventItem.customer)}`
|
||||
: `${moment(eventItem?.start_time).format('hh:mm A')}: ${eventItem.title}`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sx__event-modal__time with-padding">{ currentTab === 'medicalCalendar' ? `provider: ${eventItem?.doctor}` : eventItem?.description}</div>
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Vehicle: ${eventItem?.target_name}`}</div>}
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Deadline: ${moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}`}</div>}
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const handleClose = () => {
|
||||
setNewEventDescription('');
|
||||
setNewEventTitle('');
|
||||
setNewEventLocation('');
|
||||
setNewEventSource(undefined);
|
||||
setNewEventTarget(undefined);
|
||||
setNewEventStartDateTime(undefined);
|
||||
setNewEventType('');
|
||||
setNewEventFutureDate(undefined);
|
||||
setNewEventSourceType('');
|
||||
setNewEventTargetType('');
|
||||
setShowCreationModal(false);
|
||||
setNewEventEndDateTime(undefined);
|
||||
}
|
||||
const handleSave = () => {
|
||||
const data = {
|
||||
title: newEventTitle,
|
||||
description: newEventDescription,
|
||||
type: newEventType,
|
||||
department: newEventDepartment,
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
color: newEventColor,
|
||||
source_type: newEventSourceType,
|
||||
source_uuid: newEventSource?.value,
|
||||
source_name: newEventSource?.label,
|
||||
target_type: newEventTargetType,
|
||||
target_uuid: newEventTarget?.value,
|
||||
target_name: newEventTarget?.label,
|
||||
event_location: newEventLocation,
|
||||
event_prediction_date: newEventFutureDate && moment(newEventFutureDate).format('MM/DD/YYYY'),
|
||||
event_reminder_type: newEventReminderType,
|
||||
rrule: newEventRecurring,
|
||||
status: 'active',
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
create_date: new Date(),
|
||||
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
||||
}
|
||||
EventsService.createNewEvent(data).then(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
||||
setAllEvents(data.data);
|
||||
setShowCreationModal(false);
|
||||
setNewEventDescription('');
|
||||
setNewEventTitle('');
|
||||
setNewEventLocation('');
|
||||
setNewEventSource(undefined);
|
||||
setNewEventTarget(undefined);
|
||||
setNewEventStartDateTime(undefined);
|
||||
setNewEventType('');
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Calendar
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
Calendar
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container" style={{"min-width": "1500px"}}>
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="medicalCalendar" id="medical-calendar-tab" onSelect={(k) => goToTab(k)}>
|
||||
<Tab eventKey="medicalCalendar" title="Medical Appointments">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="activitiesCalendar" title="Activities">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="incidentsCalendar" title="Important Notes And Incidents">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="reminderDatesCalendar" title="Important Dates">
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
{ calendarView}
|
||||
<div className="list-func-panel">
|
||||
<Dropdown
|
||||
key={'event-calendar-filter'}
|
||||
id="event-calendar-filter"
|
||||
show={showFilterDropdown}
|
||||
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
<Filter size={16} className="me-2"></Filter>Filter
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenu}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Modal show={showCreationModal && currentTab !== 'medicalCalendar'} onHide={handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Create New Calendar Item</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Title
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" placeholder="Briefly Describe activity, incident, menu or reminder" value={newEventTitle || ''} onChange={e => setNewEventTitle(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 type="text" placeholder="Details for activity, incident, menu item and ingredients or reminder," value={newEventDescription || ''} onChange={e => setNewEventDescription(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Type
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<div className="field-value">{newEventType}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Want event to be recurring?
|
||||
<select value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="FREQ=YEARLY">Yearly</option>
|
||||
<option value="FREQ=MONTHLY">Monthly</option>
|
||||
<option value="FREQ=WEEKLY">Weekly</option>
|
||||
<option value="FREQ=DAILY">Daily</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Start Time
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<DatePicker
|
||||
selected={newEventStartDateTime}
|
||||
onChange={setNewEventStartDateTime}
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="MM/dd/yyyy, HH:mm"
|
||||
></DatePicker>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">End Time
|
||||
<span className="required">*</span>
|
||||
<span className="field-blurb float-right">Make sure end time is later than start time </span>
|
||||
</div>
|
||||
<DatePicker
|
||||
selected={newEventEndDateTime}
|
||||
onChange={setNewEventEndDateTime}
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="MM/dd/yyyy, HH:mm"
|
||||
></DatePicker>
|
||||
</div>
|
||||
</div>
|
||||
{ currentTab !== 'mealPlanCalendar' && <> <div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Who will we take care of in this event:</div>
|
||||
<select value={newEventTargetType} onChange={(e) => setNewEventTargetType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="vehicle">Vehicle</option>
|
||||
<option value="customer">Customer</option>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="notApplicable">Not Applicable</option>
|
||||
</select>
|
||||
</div>
|
||||
{newEventTargetType && newEventTargetType !== '' && newEventTargetType !== 'notApplicable' && <div className="me-4">
|
||||
<div className="field-label">Please select a candidate</div>
|
||||
<select value={newEventTarget?.value || ''} onChange={(e) => {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
setNewEventTarget({
|
||||
value: selectedOption.value,
|
||||
label: selectedOption.text
|
||||
})
|
||||
}}>
|
||||
<option value=""></option>
|
||||
{
|
||||
newEventTargetType === 'vehicle' && vehicles.map((item) => <option value={item?.id}>{item?.vehicle_number}</option>)
|
||||
}
|
||||
{
|
||||
newEventTargetType === 'customer' && customers.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
{
|
||||
newEventTargetType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
|
||||
</select>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Who will be responsible for this event</div>
|
||||
<select value={newEventSourceType} onChange={(e) => setNewEventSourceType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="resource">Resource</option>
|
||||
<option value="notApplicable">Not Applicable</option>
|
||||
</select>
|
||||
</div>
|
||||
{newEventSourceType && newEventSourceType !== '' && newEventSourceType !== 'notApplicable' && <div className="me-4">
|
||||
<div className="field-label">Please select a member</div>
|
||||
<select value={newEventSource?.value || ''} onChange={(e) => {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
setNewEventSource({
|
||||
value: selectedOption.value,
|
||||
label: selectedOption.text
|
||||
})
|
||||
}}>
|
||||
<option value=""></option>
|
||||
{
|
||||
newEventSourceType === 'resource' && resources.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
{
|
||||
newEventSourceType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
|
||||
</select>
|
||||
</div>}
|
||||
</div> </>}
|
||||
<div className="app-main-content-fields-section">
|
||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
||||
<div className="field-label">Reminder Type
|
||||
</div>
|
||||
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="birthday">Birthday</option>
|
||||
<option value="membership">Membership Renew</option>
|
||||
<option value="payment">Customer Payment Due Date</option>
|
||||
<option value="insurance_renew">Customer Insurance Renew</option>
|
||||
<option value="insurance_expire">Vehicle Insurance Renew</option>
|
||||
<option value="title_expire">Vehicle Title Registration</option>
|
||||
<option value="emission_test">Vehicle Emission Test</option>
|
||||
<option value="oil_change">Vehicle Oil Change</option>
|
||||
</select>
|
||||
</div>}
|
||||
{currentTab === 'activitiesCalendar' && <div className="me-4">
|
||||
<div className="field-label">Event Location
|
||||
</div>
|
||||
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
||||
</div>}
|
||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
||||
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
|
||||
</div>
|
||||
<DatePicker selected={newEventFutureDate}
|
||||
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Color
|
||||
</div>
|
||||
<select value={newEventColor} onChange={e => setNewEventColor(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{
|
||||
EventsService.colorOptions?.map((item) => <option value={item?.value}>{item?.label}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSave}>
|
||||
Save Calendar Item
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default EventsCalendar;
|
||||
751
client/src/components/center-calendar/CenterCalendar.js.bak2
Normal file
751
client/src/components/center-calendar/CenterCalendar.js.bak2
Normal file
@ -0,0 +1,751 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthService, EventsService, CustomerService, ResourceService, VehicleService, EmployeeService } from "../../services";
|
||||
import moment from 'moment';
|
||||
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown } from "react-bootstrap";
|
||||
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
|
||||
import {
|
||||
viewMonthGrid,
|
||||
createViewDay,
|
||||
createViewWeek,
|
||||
createViewMonthGrid
|
||||
} from '@schedule-x/calendar';
|
||||
import { createEventsServicePlugin } from '@schedule-x/events-service';
|
||||
import { createEventModalPlugin} from '@schedule-x/event-modal';
|
||||
import { createEventRecurrencePlugin } from "@schedule-x/event-recurrence";
|
||||
import '@schedule-x/theme-default/dist/calendar.css';
|
||||
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { vehicleSlice } from "../../store";
|
||||
// import { Scheduler } from "@aldabil/react-scheduler";
|
||||
|
||||
|
||||
const EventsCalendar = () => {
|
||||
const navigate = useNavigate();
|
||||
const [events, setEvents] = useState([]);
|
||||
const [allEvents, setAllEvents] = useState([]);
|
||||
const [targetedEventType, setTargetedEventType] = useState('medical');
|
||||
const [currentTab, setCurrentTab] = useState('medicalCalendar');
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [resources, setResources] = useState([]);
|
||||
const [fromDate, setFromDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
||||
const [toDate, setToDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0));
|
||||
const [currentTotalTranslate1, setCurrentTotalTranslate1] = useState(0);
|
||||
const [currentTotalTranslate2, setCurrentTotalTranslate2] = useState(0);
|
||||
const [currentTotalResource, setCurrentTotalResource] = useState(0);
|
||||
const [showDeletedItems, setShowDeletedItems] = useState(false);
|
||||
const [timeData, setTimeData] = useState([]);
|
||||
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
||||
const eventsServicePlugin = createEventsServicePlugin();
|
||||
const eventModalService = createEventModalPlugin();
|
||||
const eventRecurrence = createEventRecurrencePlugin();
|
||||
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
||||
const [currentView, setCurrentView] = useState('month');
|
||||
const [currentViewDate, setCurrentViewDate] = useState(new Date());
|
||||
const [showCreationModal, setShowCreationModal] = useState(false);
|
||||
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
|
||||
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date())
|
||||
const [newEventType, setNewEventType] = useState('');
|
||||
const [newEventTitle, setNewEventTitle] = useState('');
|
||||
const [newEventDescription, setNewEventDescription] = useState('');
|
||||
const [newEventLocation, setNewEventLocation] = useState('');
|
||||
const [newEventTarget, setNewEventTarget] = useState(undefined);
|
||||
const [newEventSource, setNewEventSource] = useState(undefined);
|
||||
const [newEventDepartment, setNewEventDepartment] = useState('');
|
||||
const [newEventColor, setNewEventColor] = useState('');
|
||||
const [newEventSourceType, setNewEventSourceType] = useState('');
|
||||
const [newEventTargetType, setNewEventTargetType] = useState('');
|
||||
const [newEventFutureDate, setNewEventFutureDate] = useState(undefined);
|
||||
const [newEventReminderType, setNewEventReminderType] = useState('');
|
||||
const [newEventRecurring, setNewEventRecurring] = useState(undefined);
|
||||
const [vehicles, setVehicles] = useState([]);
|
||||
const [employees, setEmployees] = useState([]);
|
||||
|
||||
// Helper function to format name from "lastname, firstname" to "firstname lastname"
|
||||
const formatFullName = (name) => {
|
||||
if (!name) return '';
|
||||
if (name.includes(',')) {
|
||||
const parts = name.split(',').map(part => part.trim());
|
||||
return `${parts[1]} ${parts[0]}`; // firstname lastname
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
// Helper function to get shortened name
|
||||
const getShortenedName = (name) => {
|
||||
if (!name) return '';
|
||||
const fullName = formatFullName(name);
|
||||
const parts = fullName.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
return `${parts[0]} ${parts[parts.length - 1].charAt(0)}`; // FirstName L
|
||||
}
|
||||
return fullName;
|
||||
};
|
||||
|
||||
// Helper function to format event title
|
||||
const formatEventTitle = (customerName, startTime) => {
|
||||
const fullName = formatFullName(customerName);
|
||||
const timeStr = startTime ? moment(new Date(startTime)).format('hh:mm A') : '';
|
||||
|
||||
// Try: "Time: Full Name"
|
||||
const titleWithTime = timeStr ? `${timeStr}: ${fullName}` : fullName;
|
||||
if (titleWithTime.length <= 35) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
// Try: "Full Name"
|
||||
if (fullName.length <= 35) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
// Use: "FirstName L"
|
||||
return getShortenedName(customerName);
|
||||
};
|
||||
|
||||
const eventTypeMap = {
|
||||
medicalCalendar: 'medical',
|
||||
activitiesCalendar: 'activity',
|
||||
incidentsCalendar: 'incident',
|
||||
mealPlanCalendar: 'meal_plan',
|
||||
reminderDatesCalendar: 'reminder'
|
||||
}
|
||||
|
||||
const calendar = useCalendarApp({
|
||||
views: [createViewMonthGrid(), createViewDay(), createViewWeek()],
|
||||
monthGridOptions: {
|
||||
/**
|
||||
* Number of events to display in a day cell before the "+ N events" button is shown
|
||||
* */
|
||||
nEventsPerDay: 50,
|
||||
},
|
||||
defaultView: viewMonthGrid.name,
|
||||
skipValidation: true,
|
||||
selectedDate: moment(new Date()).format('YYYY-MM-DD HH:mm'),
|
||||
events: events,
|
||||
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
|
||||
callbacks: {
|
||||
onSelectedDateUpdate(date) {
|
||||
setCurrentViewDate(new Date(date));
|
||||
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
|
||||
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
|
||||
},
|
||||
onViewChange(view) {
|
||||
setCurrentView(view);
|
||||
},
|
||||
onClickDate(date) {
|
||||
setNewEventStartDateTime(new Date(date))
|
||||
setNewEventEndDateTime(new Date(date));
|
||||
setShowCreationModal(true);
|
||||
},
|
||||
onClickDateTime(dateTime) {
|
||||
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
|
||||
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
|
||||
setShowCreationModal(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Filter events based on current view
|
||||
const getFilteredEvents = () => {
|
||||
const viewDate = moment(currentViewDate);
|
||||
|
||||
// Check for day view
|
||||
if (currentView && currentView.toLowerCase().includes('day')) {
|
||||
// Show only events for the selected day
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isSame(viewDate, 'day');
|
||||
});
|
||||
}
|
||||
// Check for week view
|
||||
else if (currentView && currentView.toLowerCase().includes('week')) {
|
||||
// Show events for the week containing the selected date
|
||||
const startOfWeek = viewDate.clone().startOf('week');
|
||||
const endOfWeek = viewDate.clone().endOf('week');
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isBetween(startOfWeek, endOfWeek, 'day', '[]');
|
||||
});
|
||||
}
|
||||
// Month view (including month-grid and month-agenda)
|
||||
else {
|
||||
return events.filter(event => {
|
||||
const eventDate = moment(event.start_time);
|
||||
return eventDate.isSame(viewDate, 'month');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getGroupedEvents = () => {
|
||||
const eventsDateMap = new Map();
|
||||
const filteredEvents = getFilteredEvents();
|
||||
for (const eventItem of filteredEvents) {
|
||||
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||
if (eventsDateMap.has(dateString)) {
|
||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||
} else {
|
||||
const value = [];
|
||||
value.push(eventItem);
|
||||
eventsDateMap.set(dateString, value);
|
||||
}
|
||||
}
|
||||
return eventsDateMap;
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
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();
|
||||
navigate(`/login`);
|
||||
}
|
||||
VehicleService.getAllActiveVehicles().then((data) => {
|
||||
setVehicles(data.data)
|
||||
});
|
||||
EmployeeService.getAllEmployees().then((data) => {
|
||||
setEmployees(data.data);
|
||||
});
|
||||
CustomerService.getAllCustomers().then((data) => {
|
||||
setCustomers(data.data);
|
||||
});
|
||||
ResourceService.getAll().then((data) => {
|
||||
setResources(data.data);
|
||||
});
|
||||
EventsService.getTimeData().then(data => {
|
||||
setTimeData(data.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
|
||||
}, [fromDate, toDate]);
|
||||
|
||||
useEffect(() => {
|
||||
setNewEventType(eventTypeMap[currentTab]);
|
||||
if (currentTab === 'medicalCalendar') {
|
||||
if (customers?.length > 0 && resources.length > 0) {
|
||||
const orignialEvents = [...allEvents];
|
||||
setEvents(orignialEvents?.filter(item => item.type === 'medical')?.map((item) => {
|
||||
const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
|
||||
const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
|
||||
item.event_id = item.id;
|
||||
item.customer = customerField;
|
||||
item.doctor = doctorField;
|
||||
item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
|
||||
item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
|
||||
item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
|
||||
item.translation = item?.data?.interpreter || '';
|
||||
item.newPatient = item?.data?.new_patient || '';
|
||||
item.needId = item?.data?.need_id || '';
|
||||
item.disability = item?.data?.disability || '';
|
||||
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
||||
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
||||
item.fasting = item?.data?.fasting || '';
|
||||
item.transportation = item?.link_event_name || '';
|
||||
item.title = currentTab==='medicalCalendar' ? formatEventTitle(customerField, item?.start_time) : item.title;
|
||||
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
|
||||
item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||
const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData);
|
||||
const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
|
||||
item.color = item?.color;
|
||||
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
||||
item.showWarnings = isFutureEvent;
|
||||
item.maxTranslate1 = maxTranslate1;
|
||||
item.maxTranslate2 = maxTranslate2;
|
||||
item.maxResource = maxResource;
|
||||
item.totalTranslate1 = totalTranslate1;
|
||||
setCurrentTotalTranslate1(item.totalTranslate1);
|
||||
item.totalTranslate2 = totalTranslate2;
|
||||
setCurrentTotalTranslate2(item.totalTranslate2);
|
||||
item.totalResource = totalResource;
|
||||
setCurrentTotalResource(item.totalResource);
|
||||
return item;
|
||||
})?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
|
||||
}
|
||||
} else {
|
||||
const originalEvents = [...allEvents];
|
||||
setEvents(originalEvents?.filter(item => item.type === eventTypeMap[currentTab])?.map(item => ({
|
||||
...item,
|
||||
title: item?.title,
|
||||
start: item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`,
|
||||
end: item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`),
|
||||
_options: { additionalClasses: [`event-${item?.color || 'primary'}`]}
|
||||
}))?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
|
||||
}
|
||||
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
|
||||
|
||||
useEffect(() => {
|
||||
if (events && calendar) {
|
||||
calendar?.eventsService?.set(events);
|
||||
setGroupedEvents(getGroupedEvents());
|
||||
}
|
||||
}, [events, currentView, currentViewDate]);
|
||||
|
||||
|
||||
const redirectToAdmin = () => {
|
||||
navigate(`/medical`)
|
||||
}
|
||||
|
||||
const goToEdit = (id) => {
|
||||
navigate(`/medical/events/edit/${id}?from=calendar`)
|
||||
}
|
||||
|
||||
const goToCreateNew = () => {
|
||||
navigate(`/medical/events`)
|
||||
}
|
||||
|
||||
const goToList = () => {
|
||||
navigate(`/medical/events/list`)
|
||||
}
|
||||
|
||||
const goToMultipleList = () => {
|
||||
navigate(`/medical/events/multiple-list`)
|
||||
}
|
||||
|
||||
|
||||
const goToView = (id) => {
|
||||
navigate(`/medical/events/${id}`)
|
||||
}
|
||||
|
||||
const disableEvent = (id) => {
|
||||
const currentEvent = events.find(item => item.id === id);
|
||||
EventsService.disableEvent(id, { status: 'inactive', edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
edit_history: currentEvent?.edit_history? [...currentEvent.edit_history, { employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] : [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]}).then(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
||||
setAllEvents(data?.data);
|
||||
// if (currentTab === 'medicalCalendar') {
|
||||
// setEvents(data.data.filter((item) => {
|
||||
// const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
|
||||
// const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
|
||||
// item.event_id = item.id;
|
||||
// item.customer = customerField;
|
||||
// item.doctor = doctorField;
|
||||
// item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
|
||||
// item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
|
||||
// item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
|
||||
// item.translation = item?.data?.interpreter || '';
|
||||
// item.newPatient = item?.data?.new_patient || '';
|
||||
// item.needId = item?.data?.need_id || '';
|
||||
// item.disability = item?.data?.disability || '';
|
||||
// item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}` ;
|
||||
// item.endTime = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||
// item.fasting = item?.data?.fasting || '';
|
||||
// item.transportation = item?.link_event_name || '';
|
||||
// item.title = `${customerField}, provider: ${doctorField}`;
|
||||
// item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
|
||||
// item.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
||||
// item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
||||
// const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
|
||||
// const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
|
||||
// item.color = item?.color;
|
||||
// item.showWarnings = isFutureEvent;
|
||||
// item.maxTranslate1 = maxTranslate1;
|
||||
// item.maxTranslate2 = maxTranslate2;
|
||||
// item.maxResource = maxResource;
|
||||
// item.totalTranslate1 = totalTranslate1;
|
||||
// setCurrentTotalTranslate1(item.totalTranslate1);
|
||||
// item.totalTranslate2 = totalTranslate2;
|
||||
// setCurrentTotalTranslate2(item.totalTranslate2);
|
||||
// item.totalResource = totalResource;
|
||||
// setCurrentTotalResource(item.totalResource);
|
||||
// return item;
|
||||
// }).filter(item => item.type === 'medical'));
|
||||
// }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const FilterAndClose = () => {
|
||||
setShowFilterDropdown(false);
|
||||
}
|
||||
|
||||
const cleanFilterAndClose = () => {
|
||||
setShowFilterDropdown(false);
|
||||
setShowDeletedItems(false);
|
||||
}
|
||||
|
||||
const goToTab = (value) => {
|
||||
setTargetedEventType(eventTypeMap[value]);
|
||||
setCurrentTab(value);
|
||||
}
|
||||
|
||||
|
||||
const customComponents = {
|
||||
eventModal: ({calendarEvent}) => {
|
||||
return <>
|
||||
|
||||
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</div>
|
||||
{ calendarEvent?.doctor && <div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</div>}
|
||||
<div className="sx__event-modal__time">{`${calendarEvent?.start}`}</div>
|
||||
<div className="sx__event-modal__time">
|
||||
{currentTab === 'medicalCalendar' && <PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>}
|
||||
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </div>
|
||||
</>
|
||||
}
|
||||
};
|
||||
|
||||
const customMenu = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={className}
|
||||
aria-labelledby={labeledBy}
|
||||
>
|
||||
<h6>Filter By</h6>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Show Deleted Events</div>
|
||||
<input type="checkbox" value={showDeletedItems} checked={showDeletedItems === true} onClick={() => setShowDeletedItems(!showDeletedItems)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => cleanFilterAndClose()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const calendarView = <div className="multi-columns-container">
|
||||
<div className="column-container" style={{'minWidth': '1000px'}}>
|
||||
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
||||
</div>
|
||||
<div className="column-container">
|
||||
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden' }}>
|
||||
<h6 className="text-primary me-4">List</h6>
|
||||
{
|
||||
Array.from(groupedEvents?.keys())?.map((key) => {
|
||||
return <div key={key}>
|
||||
<h6 className="text-primary me-2">{key}</h6>
|
||||
{
|
||||
groupedEvents.get(key).map(eventItem => <div
|
||||
key={eventItem.id}
|
||||
className={`event-${eventItem.color || 'primary'} mb-4 event-list-item-container`}
|
||||
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
|
||||
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default' }}
|
||||
>
|
||||
<div className="event-item-flex">
|
||||
<div className="sx__month-agenda-event__title">
|
||||
{currentTab === 'medicalCalendar'
|
||||
? `${moment(eventItem?.start_time).format('hh:mm A')}: ${formatFullName(eventItem.customer)}`
|
||||
: `${moment(eventItem?.start_time).format('hh:mm A')}: ${eventItem.title}`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sx__event-modal__time with-padding">{ currentTab === 'medicalCalendar' ? `provider: ${eventItem?.doctor}` : eventItem?.description}</div>
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Vehicle: ${eventItem?.target_name}`}</div>}
|
||||
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Deadline: ${moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}`}</div>}
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const handleClose = () => {
|
||||
setNewEventDescription('');
|
||||
setNewEventTitle('');
|
||||
setNewEventLocation('');
|
||||
setNewEventSource(undefined);
|
||||
setNewEventTarget(undefined);
|
||||
setNewEventStartDateTime(undefined);
|
||||
setNewEventType('');
|
||||
setNewEventFutureDate(undefined);
|
||||
setNewEventSourceType('');
|
||||
setNewEventTargetType('');
|
||||
setShowCreationModal(false);
|
||||
setNewEventEndDateTime(undefined);
|
||||
}
|
||||
const handleSave = () => {
|
||||
const data = {
|
||||
title: newEventTitle,
|
||||
description: newEventDescription,
|
||||
type: newEventType,
|
||||
department: newEventDepartment,
|
||||
start_time: newEventStartDateTime,
|
||||
stop_time: newEventStartDateTime,
|
||||
color: newEventColor,
|
||||
source_type: newEventSourceType,
|
||||
source_uuid: newEventSource?.value,
|
||||
source_name: newEventSource?.label,
|
||||
target_type: newEventTargetType,
|
||||
target_uuid: newEventTarget?.value,
|
||||
target_name: newEventTarget?.label,
|
||||
event_location: newEventLocation,
|
||||
event_prediction_date: newEventFutureDate && moment(newEventFutureDate).format('MM/DD/YYYY'),
|
||||
event_reminder_type: newEventReminderType,
|
||||
rrule: newEventRecurring,
|
||||
status: 'active',
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
edit_date: new Date(),
|
||||
create_date: new Date(),
|
||||
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
|
||||
}
|
||||
EventsService.createNewEvent(data).then(() => {
|
||||
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
||||
setAllEvents(data.data);
|
||||
setShowCreationModal(false);
|
||||
setNewEventDescription('');
|
||||
setNewEventTitle('');
|
||||
setNewEventLocation('');
|
||||
setNewEventSource(undefined);
|
||||
setNewEventTarget(undefined);
|
||||
setNewEventStartDateTime(undefined);
|
||||
setNewEventType('');
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Calendar
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
Calendar
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container" style={{"min-width": "1500px"}}>
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="medicalCalendar" id="medical-calendar-tab" onSelect={(k) => goToTab(k)}>
|
||||
<Tab eventKey="medicalCalendar" title="Medical Appointments">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="activitiesCalendar" title="Activities">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="incidentsCalendar" title="Important Notes And Incidents">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
|
||||
|
||||
</Tab>
|
||||
<Tab eventKey="reminderDatesCalendar" title="Important Dates">
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
{ calendarView}
|
||||
<div className="list-func-panel">
|
||||
<Dropdown
|
||||
key={'event-calendar-filter'}
|
||||
id="event-calendar-filter"
|
||||
show={showFilterDropdown}
|
||||
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
<Filter size={16} className="me-2"></Filter>Filter
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenu}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Modal show={showCreationModal && currentTab !== 'medicalCalendar'} onHide={handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Create New Calendar Item</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Title
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" placeholder="Briefly Describe activity, incident, menu or reminder" value={newEventTitle || ''} onChange={e => setNewEventTitle(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 type="text" placeholder="Details for activity, incident, menu item and ingredients or reminder," value={newEventDescription || ''} onChange={e => setNewEventDescription(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Type
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<div className="field-value">{newEventType}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Want event to be recurring?
|
||||
<select value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="FREQ=YEARLY">Yearly</option>
|
||||
<option value="FREQ=MONTHLY">Monthly</option>
|
||||
<option value="FREQ=WEEKLY">Weekly</option>
|
||||
<option value="FREQ=DAILY">Daily</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Start Time
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<DatePicker
|
||||
selected={newEventStartDateTime}
|
||||
onChange={setNewEventStartDateTime}
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="MM/dd/yyyy, HH:mm"
|
||||
></DatePicker>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">End Time
|
||||
<span className="required">*</span>
|
||||
<span className="field-blurb float-right">Make sure end time is later than start time </span>
|
||||
</div>
|
||||
<DatePicker
|
||||
selected={newEventEndDateTime}
|
||||
onChange={setNewEventEndDateTime}
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="MM/dd/yyyy, HH:mm"
|
||||
></DatePicker>
|
||||
</div>
|
||||
</div>
|
||||
{ currentTab !== 'mealPlanCalendar' && <> <div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Who will we take care of in this event:</div>
|
||||
<select value={newEventTargetType} onChange={(e) => setNewEventTargetType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="vehicle">Vehicle</option>
|
||||
<option value="customer">Customer</option>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="notApplicable">Not Applicable</option>
|
||||
</select>
|
||||
</div>
|
||||
{newEventTargetType && newEventTargetType !== '' && newEventTargetType !== 'notApplicable' && <div className="me-4">
|
||||
<div className="field-label">Please select a candidate</div>
|
||||
<select value={newEventTarget?.value || ''} onChange={(e) => {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
setNewEventTarget({
|
||||
value: selectedOption.value,
|
||||
label: selectedOption.text
|
||||
})
|
||||
}}>
|
||||
<option value=""></option>
|
||||
{
|
||||
newEventTargetType === 'vehicle' && vehicles.map((item) => <option value={item?.id}>{item?.vehicle_number}</option>)
|
||||
}
|
||||
{
|
||||
newEventTargetType === 'customer' && customers.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
{
|
||||
newEventTargetType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
|
||||
</select>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Who will be responsible for this event</div>
|
||||
<select value={newEventSourceType} onChange={(e) => setNewEventSourceType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="employee">Employee</option>
|
||||
<option value="resource">Resource</option>
|
||||
<option value="notApplicable">Not Applicable</option>
|
||||
</select>
|
||||
</div>
|
||||
{newEventSourceType && newEventSourceType !== '' && newEventSourceType !== 'notApplicable' && <div className="me-4">
|
||||
<div className="field-label">Please select a member</div>
|
||||
<select value={newEventSource?.value || ''} onChange={(e) => {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
setNewEventSource({
|
||||
value: selectedOption.value,
|
||||
label: selectedOption.text
|
||||
})
|
||||
}}>
|
||||
<option value=""></option>
|
||||
{
|
||||
newEventSourceType === 'resource' && resources.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
{
|
||||
newEventSourceType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
|
||||
}
|
||||
|
||||
</select>
|
||||
</div>}
|
||||
</div> </>}
|
||||
<div className="app-main-content-fields-section">
|
||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
||||
<div className="field-label">Reminder Type
|
||||
</div>
|
||||
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
|
||||
<option value=""></option>
|
||||
<option value="birthday">Birthday</option>
|
||||
<option value="membership">Membership Renew</option>
|
||||
<option value="payment">Customer Payment Due Date</option>
|
||||
<option value="insurance_renew">Customer Insurance Renew</option>
|
||||
<option value="insurance_expire">Vehicle Insurance Renew</option>
|
||||
<option value="title_expire">Vehicle Title Registration</option>
|
||||
<option value="emission_test">Vehicle Emission Test</option>
|
||||
<option value="oil_change">Vehicle Oil Change</option>
|
||||
</select>
|
||||
</div>}
|
||||
{currentTab === 'activitiesCalendar' && <div className="me-4">
|
||||
<div className="field-label">Event Location
|
||||
</div>
|
||||
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
||||
</div>}
|
||||
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
|
||||
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
|
||||
</div>
|
||||
<DatePicker selected={newEventFutureDate}
|
||||
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Color
|
||||
</div>
|
||||
<select value={newEventColor} onChange={e => setNewEventColor(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{
|
||||
EventsService.colorOptions?.map((item) => <option value={item?.value}>{item?.label}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSave}>
|
||||
Save Calendar Item
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default EventsCalendar;
|
||||
311
client/src/components/trans-routes/CreateTemplateRoute.js
Normal file
311
client/src/components/trans-routes/CreateTemplateRoute.js
Normal file
@ -0,0 +1,311 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
|
||||
import { Modal, Button, Breadcrumb, Tabs, Tab } from "react-bootstrap";
|
||||
import RouteCustomerEditor from "./RouteCustomerEditor";
|
||||
import { AuthService, DailyRoutesTemplateService } from "../../services";
|
||||
|
||||
const CreateTemplateRoute = () => {
|
||||
const params = useParams();
|
||||
const templateId = params.id;
|
||||
const routeType = new URLSearchParams(window.location.search).get('type');
|
||||
|
||||
const drivers = useSelector(selectAllActiveDrivers);
|
||||
const vehicles = useSelector(selectAllActiveVehicles);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [routeName, setRouteName] = useState('');
|
||||
const [newDriver, setNewDriver] = useState('');
|
||||
const [newVehicle, setNewVehicle] = useState('');
|
||||
const [newRouteType, setNewRouteType] = useState(routeType || 'inbound');
|
||||
const [newCustomerList, setNewCustomerList] = useState([]);
|
||||
const [errorMessage, setErrorMessage] = useState(undefined);
|
||||
const [disableSave, setDisableSave] = useState(false);
|
||||
const [template, setTemplate] = useState(null);
|
||||
|
||||
const currentVehicle = vehicles.find((vehicle) => vehicle.id === newVehicle);
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditRoutes()) {
|
||||
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();
|
||||
navigate(`/login`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch the template to get its name
|
||||
DailyRoutesTemplateService.getDailyRoutesTemplate(templateId).then(data => {
|
||||
setTemplate(data.data);
|
||||
});
|
||||
}, [templateId]);
|
||||
|
||||
const redirectToTemplate = () => {
|
||||
navigate(`/trans-routes/daily-templates/view/${templateId}`);
|
||||
}
|
||||
|
||||
const validateRoute = () => {
|
||||
const errors = [];
|
||||
|
||||
// Required fields validation
|
||||
if (!routeName || routeName.trim() === '') {
|
||||
errors.push('Route Name');
|
||||
}
|
||||
if (!newRouteType || newRouteType === '') {
|
||||
errors.push('Route Type');
|
||||
}
|
||||
if (!newDriver || newDriver === '') {
|
||||
errors.push('Driver');
|
||||
}
|
||||
if (!newVehicle || newVehicle === '') {
|
||||
errors.push('Vehicle');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveRoute = () => {
|
||||
if (!disableSave) {
|
||||
if (!validateRoute()) {
|
||||
return;
|
||||
}
|
||||
setDisableSave(true);
|
||||
|
||||
// Fetch the current template
|
||||
DailyRoutesTemplateService.getDailyRoutesTemplate(templateId).then(response => {
|
||||
const currentTemplate = response.data;
|
||||
|
||||
// Create the new route object
|
||||
const newRoute = {
|
||||
name: routeName,
|
||||
route_customer_list: newCustomerList,
|
||||
driver: newDriver,
|
||||
vehicle: newVehicle,
|
||||
type: newRouteType,
|
||||
start_mileage: currentVehicle.mileage,
|
||||
status: [],
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
estimated_start_time: null,
|
||||
checklist_result: []
|
||||
};
|
||||
|
||||
// Add the new route to the template's routes array
|
||||
const updatedRoutes = [...(currentTemplate.routes || []), newRoute];
|
||||
|
||||
// Update the template
|
||||
const updatedTemplate = {
|
||||
...currentTemplate,
|
||||
routes: updatedRoutes
|
||||
};
|
||||
|
||||
DailyRoutesTemplateService.updateDailyRoutesTemplate(templateId, updatedTemplate)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
setDisableSave(false);
|
||||
redirectToTemplate();
|
||||
}, 1000);
|
||||
})
|
||||
.catch(err => {
|
||||
setDisableSave(false);
|
||||
setErrorMessage('Failed to save route to template');
|
||||
});
|
||||
}).catch(err => {
|
||||
setDisableSave(false);
|
||||
setErrorMessage('Failed to fetch template');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
|
||||
Daily Route Templates
|
||||
</Breadcrumb.Item>
|
||||
{template && (
|
||||
<Breadcrumb.Item href={`/trans-routes/daily-templates/view/${templateId}`}>
|
||||
{template.name}
|
||||
</Breadcrumb.Item>
|
||||
)}
|
||||
<Breadcrumb.Item active>
|
||||
Create New Route
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
Create New Route for Template <button className="btn btn-link btn-sm" onClick={() => {redirectToTemplate()}}>Back</button>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
|
||||
<Tab eventKey="routeOverview" title="Route Information">
|
||||
<div className="multi-columns-container">
|
||||
<div className="column-container">
|
||||
<div className="column-card">
|
||||
<h6 className="text-primary">Route Details</h6>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Route Name
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<input type="text" value={routeName || ''} onChange={e => setRouteName(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Vechile
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<select value={newVehicle} onChange={e => setNewVehicle(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{vehicles.map((vehicle) => (<option key={vehicle.id} value={vehicle.id}>{vehicle.vehicle_number}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Driver
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<select value={newDriver} onChange={e => setNewDriver(e.target.value)}>
|
||||
<option value=""></option>
|
||||
{drivers.map((driver) => <option key={driver.id} value={driver.id}>{driver.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Route Type
|
||||
<span className="required">*</span>
|
||||
</div>
|
||||
<select value={newRouteType} onChange={e => setNewRouteType(e.target.value)}>
|
||||
<option value="inbound">Inbound</option>
|
||||
<option value="outbound">Outbound</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column-card adjust">
|
||||
<div className="col-md-12 mb-4">
|
||||
<RouteCustomerEditor currentRoute={undefined} setNewCustomerList={setNewCustomerList}></RouteCustomerEditor>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectToTemplate()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" disabled={disableSave} onClick={() => saveRoute()}> Save </button>
|
||||
</div>
|
||||
{errorMessage && <div className="col-md-12 col-sm-12 col-xs-12 alert alert-danger mt-4">{errorMessage}</div>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="column-container">
|
||||
{ newVehicle && newVehicle !== '' && <div className="column-card mb-4">
|
||||
<h6 className="text-primary">Vehicle Information</h6>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Vehicle Number</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.vehicle_number}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Seating Capacity</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.capacity}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Mileage</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.mileage}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Make</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.make}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Model</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.model}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">License Plate</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.tag}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Year</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.year}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">GPS ID</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.gps_tag}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">EZPass</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.ezpass}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Vin</div>
|
||||
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.vin || ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
{
|
||||
newDriver && newDriver !== '' && <div className="column-card">
|
||||
<h6 className="text-primary">Driver Information</h6>
|
||||
<div className="text-primary">Personal Details</div>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Driver Name</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.name}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Preferred Name</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.name_cn}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Job Title</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.title}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Job Status</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.employment_status}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="app-main-content-fields-section short">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Driver Capacity</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.driver_capacity}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Phone Number</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.phone}</div>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field-label">Email</div>
|
||||
<div className="field-value">{drivers.find(item => item.id === newDriver)?.email}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateTemplateRoute;
|
||||
|
||||
287
client/src/components/trans-routes/DailyTemplatesList.js
Normal file
287
client/src/components/trans-routes/DailyTemplatesList.js
Normal file
@ -0,0 +1,287 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthService, DailyRoutesTemplateService } from "../../services";
|
||||
import { Breadcrumb, Modal, Button, Tabs, Tab } from "react-bootstrap";
|
||||
import { PencilSquare, Eye } from "react-bootstrap-icons";
|
||||
import ReactPaginate from 'react-paginate';
|
||||
|
||||
const DailyTemplatesList = () => {
|
||||
const navigate = useNavigate();
|
||||
const [templates, setTemplates] = useState([]);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [currentItems, setCurrentItems] = useState(null);
|
||||
const [sorting, setSorting] = useState({key: '', order: ''});
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const [itemOffset, setItemOffset] = useState(0);
|
||||
const [pageCount, setPageCount] = useState(0);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [templateToDelete, setTemplateToDelete] = useState(null);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Template Name',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'template_date',
|
||||
label: 'Template Date',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'routes_count',
|
||||
label: 'Routes Count',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'create_by',
|
||||
label: 'Created By',
|
||||
show: true
|
||||
},
|
||||
{
|
||||
key: 'create_date',
|
||||
label: 'Created Date',
|
||||
show: true
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAccessLegacySystem()) {
|
||||
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();
|
||||
navigate(`/login`);
|
||||
}
|
||||
fetchTemplates();
|
||||
}, []);
|
||||
|
||||
const fetchTemplates = () => {
|
||||
DailyRoutesTemplateService.getAll().then(data => {
|
||||
setTemplates(data.data);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const endOffset = itemOffset + parseInt(itemsPerPage);
|
||||
const newTemplates = templates?.filter(template =>
|
||||
template?.name?.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
template?.template_date?.toLowerCase().includes(keyword.toLowerCase())
|
||||
);
|
||||
|
||||
const sortedTemplates = sorting.key === '' ? newTemplates : newTemplates.sort((a, b) => {
|
||||
if (sorting.key === 'routes_count') {
|
||||
return (a.routes?.length || 0) - (b.routes?.length || 0);
|
||||
}
|
||||
return a[sorting.key]?.toString().localeCompare(b[sorting.key]?.toString());
|
||||
});
|
||||
|
||||
const results = sorting.order === 'asc' ? sortedTemplates : sortedTemplates.reverse();
|
||||
setCurrentItems(results.slice(itemOffset, endOffset)?.map(item => ({
|
||||
...item,
|
||||
routes_count: item.routes?.length || 0,
|
||||
create_date: item.create_date ? new Date(item.create_date).toLocaleDateString() : ''
|
||||
})));
|
||||
setPageCount(Math.ceil(newTemplates.length / itemsPerPage));
|
||||
}, [templates, itemOffset, keyword, itemsPerPage, sorting]);
|
||||
|
||||
const handlePageClick = (event) => {
|
||||
const newOffset = (event.selected * itemsPerPage) % templates?.filter(template =>
|
||||
template?.name?.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
template?.template_date?.toLowerCase().includes(keyword.toLowerCase())
|
||||
).length;
|
||||
setItemOffset(newOffset);
|
||||
setCurrentPage(event.selected + 1);
|
||||
};
|
||||
|
||||
const handlePageSelect = (pageNo) => {
|
||||
const newOffset = ((pageNo-1) * itemsPerPage) % templates?.filter(template =>
|
||||
template?.name?.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
template?.template_date?.toLowerCase().includes(keyword.toLowerCase())
|
||||
).length;
|
||||
setItemOffset(newOffset);
|
||||
};
|
||||
|
||||
const getSortingImg = (key) => {
|
||||
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
|
||||
}
|
||||
|
||||
const sortTableWithField = (key) => {
|
||||
let newSorting = {
|
||||
key,
|
||||
order: 'asc',
|
||||
}
|
||||
|
||||
if (sorting.key === key && sorting.order === 'asc') {
|
||||
newSorting = {...newSorting, order: 'desc'};
|
||||
}
|
||||
setSorting(newSorting);
|
||||
}
|
||||
|
||||
const goToView = (id) => {
|
||||
navigate(`/trans-routes/daily-templates/view/${id}`);
|
||||
}
|
||||
|
||||
const confirmDelete = (template) => {
|
||||
setTemplateToDelete(template);
|
||||
setShowDeleteModal(true);
|
||||
}
|
||||
|
||||
const deleteTemplate = () => {
|
||||
if (templateToDelete) {
|
||||
DailyRoutesTemplateService.deleteDailyRoutesTemplate(templateToDelete.id).then(() => {
|
||||
fetchTemplates();
|
||||
setShowDeleteModal(false);
|
||||
setTemplateToDelete(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSelectedAllItems = () => {
|
||||
if (selectedItems.length !== currentItems?.length || selectedItems?.length === 0) {
|
||||
const newSelectedItems = [...currentItems].map((template) => template.id);
|
||||
setSelectedItems(newSelectedItems);
|
||||
} else {
|
||||
setSelectedItems([]);
|
||||
}
|
||||
}
|
||||
|
||||
const toggleItem = (id) => {
|
||||
if (selectedItems.includes(id)) {
|
||||
const newSelectedItems = [...selectedItems].filter((item) => item !== id);
|
||||
setSelectedItems(newSelectedItems);
|
||||
} else {
|
||||
const newSelectedItems = [...selectedItems, id];
|
||||
setSelectedItems(newSelectedItems);
|
||||
}
|
||||
}
|
||||
|
||||
const checkSelectAll = () => {
|
||||
return selectedItems?.length === currentItems?.length && selectedItems?.length > 0;
|
||||
}
|
||||
|
||||
const table = (
|
||||
<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={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
|
||||
<th className="th-index">No.</th>
|
||||
{
|
||||
columns.filter(col => col.show).map((column, index) => <th className="sortable-header" key={index}>
|
||||
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`} alt="sort"></img></span>
|
||||
</th>)
|
||||
}
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
currentItems?.map((template, index) => <tr key={template?.id}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(template.id)} onClick={()=>toggleItem(template?.id)}/></td>
|
||||
<td className="td-index">{index + 1 + itemOffset}</td>
|
||||
{columns.find(col => col.key === 'name')?.show && <td>
|
||||
<button className="btn btn-link btn-sm" onClick={() => goToView(template?.id)}>{template?.name}</button>
|
||||
</td>}
|
||||
{columns.find(col => col.key === 'template_date')?.show && <td>{template?.template_date}</td>}
|
||||
{columns.find(col => col.key === 'routes_count')?.show && <td>{template?.routes_count}</td>}
|
||||
{columns.find(col => col.key === 'create_by')?.show && <td>{template?.create_by}</td>}
|
||||
{columns.find(col => col.key === 'create_date')?.show && <td>{template?.create_date}</td>}
|
||||
<td>
|
||||
<Eye size={16} className="clickable me-2" onClick={() => goToView(template?.id)} title="View" />
|
||||
</td>
|
||||
</tr>)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="pagination-container">
|
||||
<ReactPaginate
|
||||
key={itemsPerPage}
|
||||
className="customers-pagination"
|
||||
breakLabel="..."
|
||||
nextLabel="Next"
|
||||
onPageChange={handlePageClick}
|
||||
pageRangeDisplayed={5}
|
||||
pageCount={pageCount}
|
||||
previousLabel="Prev"
|
||||
renderOnZeroPageCount={null}
|
||||
containerClassName="pagination justify-content-center"
|
||||
pageClassName="page-item"
|
||||
pageLinkClassName="page-link"
|
||||
previousClassName="page-prev-item"
|
||||
previousLinkClassName="page-link"
|
||||
nextClassName="page-next-item"
|
||||
nextLinkClassName="page-link"
|
||||
activeClassName="active"
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
/>
|
||||
<div className="select-page-container">
|
||||
<input type="number" className="page-picker" max={pageCount} min={1} value={currentPage} onChange={(e) => {handlePageSelect(e.target.value); setCurrentPage(e.target.value)}}/>
|
||||
<div className="per-page-label"> {` of ${pageCount}`}</div>
|
||||
</div>
|
||||
|
||||
<div className="select-page-container">
|
||||
<select className="per-page" value={itemsPerPage} onChange={e => setItemsPerPage(e.target.value)}>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select> <span className="per-page-label"> /page</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Daily Route Templates
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
All Daily Route Templates
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<Tabs defaultActiveKey="dailyTemplates" id="daily-templates-tab">
|
||||
<Tab eventKey="dailyTemplates" title="Daily Route Templates">
|
||||
{table}
|
||||
</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)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Delete Template</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
Are you sure you want to delete the template "{templateToDelete?.name}"?
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => setShowDeleteModal(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" onClick={deleteTemplate}>
|
||||
Delete
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default DailyTemplatesList;
|
||||
|
||||
148
client/src/components/trans-routes/MealStatus.js
Normal file
148
client/src/components/trans-routes/MealStatus.js
Normal file
@ -0,0 +1,148 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { selectAllRoutes, selectAllBreakfastRecords, selectAllLunchRecords, selectAllSnackRecords, transRoutesSlice } from "./../../store";
|
||||
import { TransRoutesService } from "../../services";
|
||||
import BreakfastSection from './BreakfastSection';
|
||||
import LunchSection from "./LunchSection";
|
||||
import SnackSection from "./SnackSection";
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchAllRoutes());
|
||||
dispatch(fetchAllBreakfastRecords());
|
||||
dispatch(fetchAllLunchRecords());
|
||||
dispatch(fetchAllSnackRecords());
|
||||
}, []);
|
||||
|
||||
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'),
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_history: [{
|
||||
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
date: new Date()
|
||||
}]
|
||||
}).then(() => {
|
||||
dispatch(fetchAllBreakfastRecords());
|
||||
})
|
||||
}
|
||||
|
||||
const confirmHasLunch = (customer) => {
|
||||
TransRoutesService.createLunchRecords({
|
||||
customer_id: customer?.customer_id,
|
||||
customer_name: customer?.customer_name,
|
||||
has_lunch: true,
|
||||
date: moment(new Date()).format('MM/DD/YYYY'),
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_history: [{
|
||||
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
date: new Date()
|
||||
}]
|
||||
}).then(() => {
|
||||
dispatch(fetchAllLunchRecords());
|
||||
})
|
||||
}
|
||||
|
||||
const confirmHasSnack = (customer) => {
|
||||
TransRoutesService.createSnackRecords({
|
||||
customer_id: customer?.customer_id,
|
||||
customer_name: customer?.customer_name,
|
||||
has_snack: true,
|
||||
date: moment(new Date()).format('MM/DD/YYYY'),
|
||||
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
create_date: new Date(),
|
||||
edit_history: [{
|
||||
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
||||
date: new Date()
|
||||
}]
|
||||
}).then(() => {
|
||||
dispatch(fetchAllSnackRecords());
|
||||
})
|
||||
}
|
||||
|
||||
const removeBreakfastRecord = (customer_id) => {
|
||||
const breakfast = breakfastRecords.find(b => b.customer_id === customer_id)?.id;
|
||||
TransRoutesService.deleteBreakfastRecords(breakfast).then(() => {
|
||||
dispatch(fetchAllBreakfastRecords());
|
||||
})
|
||||
}
|
||||
|
||||
const removeLunchRecord = (customer_id) => {
|
||||
const lunch = lunchRecords.find(b => b.customer_id === customer_id)?.id;
|
||||
TransRoutesService.deleteLunchRecords(lunch).then(() => {
|
||||
dispatch(fetchAllLunchRecords());
|
||||
})
|
||||
}
|
||||
|
||||
const removeSnackRecord = (customer_id) => {
|
||||
const snack = snackRecords.find(b => b.customer_id === customer_id)?.id;
|
||||
TransRoutesService.deleteSnackRecords(snack).then(() => {
|
||||
dispatch(fetchAllSnackRecords());
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/">Lobby</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Meal Status
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>Meal Status</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="list row">
|
||||
<div className="col-md-4 col-sm-12 mb-4">
|
||||
<BreakfastSection
|
||||
transRoutes={allRoutes}
|
||||
breakfastRecords={breakfastRecords}
|
||||
confimHasBreakfast={confimHasBreakfast}
|
||||
removeBreakfastRecord={removeBreakfastRecord}
|
||||
sectionName={'Breakfast Info'}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4 col-sm-12 mb-4">
|
||||
<LunchSection
|
||||
transRoutes={allRoutes}
|
||||
lunchRecords={lunchRecords}
|
||||
confirmHasLunch={confirmHasLunch}
|
||||
removeLunchRecord={removeLunchRecord}
|
||||
sectionName={'Lunch Info'}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4 col-sm-12 mb-4">
|
||||
<SnackSection
|
||||
transRoutes={allRoutes}
|
||||
snackRecords={snackRecords}
|
||||
confirmHasSnack={confirmHasSnack}
|
||||
removeSnackRecord={removeSnackRecord}
|
||||
sectionName={'Snack Info'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MealStatus;
|
||||
|
||||
263
client/src/components/trans-routes/UpdateDailyTemplateRoute.js
Normal file
263
client/src/components/trans-routes/UpdateDailyTemplateRoute.js
Normal file
@ -0,0 +1,263 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { DailyRoutesTemplateService } from "../../services";
|
||||
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
|
||||
import { Breadcrumb, Tabs, Tab, Spinner, Button } from "react-bootstrap";
|
||||
import RouteCustomerEditor from "./RouteCustomerEditor";
|
||||
import { Trash } from "react-bootstrap-icons";
|
||||
|
||||
const UpdateDailyTemplateRoute = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const drivers = useSelector(selectAllActiveDrivers);
|
||||
const vehicles = useSelector(selectAllActiveVehicles);
|
||||
const [template, setTemplate] = useState(null);
|
||||
const [currentRoute, setCurrentRoute] = useState(null);
|
||||
const [routeName, setRouteName] = useState('');
|
||||
const [newDriver, setNewDriver] = useState('');
|
||||
const [newVehicle, setNewVehicle] = useState('');
|
||||
const [newRouteType, setNewRouteType] = useState('');
|
||||
const [newCustomerList, setNewCustomerList] = useState([]);
|
||||
const [errorMessage, setErrorMessage] = useState(undefined);
|
||||
const [successMessage, setSuccessMessage] = useState(undefined);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplateAndRoute();
|
||||
}, [params.id, params.routeId]);
|
||||
|
||||
const fetchTemplateAndRoute = () => {
|
||||
DailyRoutesTemplateService.getDailyRoutesTemplate(params.id).then(data => {
|
||||
setTemplate(data.data);
|
||||
const route = data.data.routes?.find(r => r._id === params.routeId);
|
||||
setCurrentRoute(route);
|
||||
setRouteName(route.name);
|
||||
setNewDriver(route.driver);
|
||||
setNewVehicle(route.vehicle);
|
||||
setNewRouteType(route.type);
|
||||
setNewCustomerList(route.route_customer_list || []);
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate(`/trans-routes/daily-templates/${params.id}/view-route/${params.routeId}`);
|
||||
};
|
||||
|
||||
const updateCurrentRoute = () => {
|
||||
try {
|
||||
if (!routeName || routeName === '') {
|
||||
setErrorMessage('Route Name is Required');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
return;
|
||||
}
|
||||
if (!newRouteType || newRouteType === '') {
|
||||
setErrorMessage('Route Type is Required');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
|
||||
// Update the route in the template
|
||||
const updatedRoutes = template.routes.map(route => {
|
||||
if (route._id === params.routeId) {
|
||||
return {
|
||||
...route,
|
||||
name: routeName,
|
||||
driver: newDriver,
|
||||
vehicle: newVehicle,
|
||||
type: newRouteType,
|
||||
route_customer_list: newCustomerList
|
||||
};
|
||||
}
|
||||
return route;
|
||||
});
|
||||
|
||||
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
|
||||
...template,
|
||||
routes: updatedRoutes
|
||||
}).then(() => {
|
||||
setSaving(false);
|
||||
setSuccessMessage('Route updated successfully!');
|
||||
setTimeout(() => {
|
||||
goBack();
|
||||
}, 1500);
|
||||
}).catch(err => {
|
||||
setSaving(false);
|
||||
setErrorMessage('Failed to update route');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
});
|
||||
} catch(ex) {
|
||||
setSaving(false);
|
||||
setErrorMessage('An error occurred');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRoute = () => {
|
||||
if (window.confirm(`Are you sure you want to delete the route "${currentRoute.name}" from this template?`)) {
|
||||
const updatedRoutes = template.routes.filter(r => r._id !== params.routeId);
|
||||
|
||||
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
|
||||
...template,
|
||||
routes: updatedRoutes
|
||||
}).then(() => {
|
||||
navigate(`/trans-routes/daily-templates/view/${params.id}`);
|
||||
}).catch(err => {
|
||||
alert('Failed to delete route');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!template || !currentRoute) {
|
||||
return (
|
||||
<div className="list row mb-4">
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
|
||||
Daily Route Templates
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href={`/trans-routes/daily-templates/view/${params.id}`}>
|
||||
{template.name}
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
Update Route: {currentRoute.name}
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
Update Route in Template
|
||||
<button className="btn btn-link btn-sm" onClick={goBack}>Cancel</button>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
{errorMessage}
|
||||
<button onClick={() => setErrorMessage(undefined)} type="button" className="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{successMessage && (
|
||||
<div className="alert alert-success alert-dismissible fade show" role="alert">
|
||||
{successMessage}
|
||||
<button onClick={() => setSuccessMessage(undefined)} type="button" className="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<Tabs defaultActiveKey="basicInfo" id="route-edit-tab">
|
||||
<Tab eventKey="basicInfo" title="Basic Information">
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="mb-3 me-2">
|
||||
<label className="form-label">Route Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={routeName}
|
||||
onChange={(e) => setRouteName(e.target.value)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Route Type *</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={newRouteType}
|
||||
onChange={(e) => setNewRouteType(e.target.value)}
|
||||
disabled={saving}
|
||||
>
|
||||
<option value="">Select Type</option>
|
||||
<option value="inbound">Inbound</option>
|
||||
<option value="outbound">Outbound</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="mb-3 me-2">
|
||||
<label className="form-label">Driver</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={newDriver}
|
||||
onChange={(e) => setNewDriver(e.target.value)}
|
||||
disabled={saving}
|
||||
>
|
||||
<option value="">Select Driver</option>
|
||||
{drivers.map((driver) => (
|
||||
<option key={driver.id} value={driver.id}>{driver.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Vehicle</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={newVehicle}
|
||||
onChange={(e) => setNewVehicle(e.target.value)}
|
||||
disabled={saving}
|
||||
>
|
||||
<option value="">Select Vehicle</option>
|
||||
{vehicles.map((vehicle) => (
|
||||
<option key={vehicle.id} value={vehicle.id}>{vehicle.vehicle_number}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="mt-4">
|
||||
<button
|
||||
className="btn btn-primary me-2"
|
||||
onClick={updateCurrentRoute}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? <><Spinner size="sm" className="me-2" />Saving...</> : 'Save Changes'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary me-2"
|
||||
onClick={goBack}
|
||||
disabled={saving}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={deleteRoute}
|
||||
disabled={saving}
|
||||
>
|
||||
<Trash size={16} className="me-2" />
|
||||
Delete Route
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="customers" title="Customers">
|
||||
<div className="app-main-content-fields-section">
|
||||
<RouteCustomerEditor
|
||||
currentRoute={currentRoute}
|
||||
setNewCustomerList={setNewCustomerList}
|
||||
/>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateDailyTemplateRoute;
|
||||
|
||||
223
client/src/components/trans-routes/ViewDailyTemplate.js
Normal file
223
client/src/components/trans-routes/ViewDailyTemplate.js
Normal file
@ -0,0 +1,223 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { DailyRoutesTemplateService } from "../../services";
|
||||
import { Breadcrumb, Modal, Button, Spinner } from "react-bootstrap";
|
||||
import { PencilSquare, Trash } from "react-bootstrap-icons";
|
||||
import RoutesSection from "./RoutesSection";
|
||||
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
|
||||
|
||||
const ViewDailyTemplate = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const drivers = useSelector(selectAllActiveDrivers);
|
||||
const vehicles = useSelector(selectAllActiveVehicles);
|
||||
const [template, setTemplate] = useState(null);
|
||||
const [inboundRoutes, setInboundRoutes] = useState([]);
|
||||
const [outboundRoutes, setOutboundRoutes] = useState([]);
|
||||
const [showEditNameModal, setShowEditNameModal] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [newName, setNewName] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplate();
|
||||
}, [params.id]);
|
||||
|
||||
const fetchTemplate = () => {
|
||||
DailyRoutesTemplateService.getDailyRoutesTemplate(params.id).then(data => {
|
||||
setTemplate(data.data);
|
||||
setNewName(data.data.name);
|
||||
|
||||
// Separate routes by type
|
||||
const inbound = data.data.routes?.filter(route => route.type === 'inbound') || [];
|
||||
const outbound = data.data.routes?.filter(route => route.type === 'outbound') || [];
|
||||
|
||||
setInboundRoutes(inbound);
|
||||
setOutboundRoutes(outbound);
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate('/trans-routes/daily-templates/list');
|
||||
};
|
||||
|
||||
const openEditNameModal = () => {
|
||||
setShowEditNameModal(true);
|
||||
};
|
||||
|
||||
const closeEditNameModal = () => {
|
||||
setShowEditNameModal(false);
|
||||
setNewName(template.name);
|
||||
};
|
||||
|
||||
const saveName = () => {
|
||||
if (!newName || newName.trim() === '') {
|
||||
alert('Please enter a template name');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
|
||||
...template,
|
||||
name: newName
|
||||
}).then(() => {
|
||||
setSaving(false);
|
||||
setShowEditNameModal(false);
|
||||
fetchTemplate();
|
||||
}).catch(err => {
|
||||
setSaving(false);
|
||||
alert('Failed to update template name');
|
||||
});
|
||||
};
|
||||
|
||||
const handleRouteClick = (routeId) => {
|
||||
navigate(`/trans-routes/daily-templates/${params.id}/view-route/${routeId}`);
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const deleteTemplate = () => {
|
||||
setDeleting(true);
|
||||
DailyRoutesTemplateService.deleteDailyRoutesTemplate(params.id).then(() => {
|
||||
setDeleting(false);
|
||||
setShowDeleteModal(false);
|
||||
navigate('/trans-routes/daily-templates/list');
|
||||
}).catch(err => {
|
||||
setDeleting(false);
|
||||
alert('Failed to delete template');
|
||||
});
|
||||
};
|
||||
|
||||
const goToCreateRoute = (type) => {
|
||||
navigate(`/trans-routes/daily-templates/${params.id}/create-route?type=${type}`);
|
||||
};
|
||||
|
||||
if (!template) {
|
||||
return (
|
||||
<div className="list row mb-4">
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
|
||||
Daily Route Templates
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
{template.name}
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
{template.name}
|
||||
<PencilSquare
|
||||
size={20}
|
||||
className="clickable ms-2"
|
||||
onClick={openEditNameModal}
|
||||
title="Edit template name"
|
||||
/>
|
||||
<button className="btn btn-link btn-sm" onClick={goBack}>Back to List</button>
|
||||
<button className="btn btn-danger btn-sm ms-2" onClick={confirmDelete}>
|
||||
<Trash size={14} className="me-1" />Delete Template
|
||||
</button>
|
||||
</h4>
|
||||
<div className="text-muted">
|
||||
<small>Template Date: {template.template_date} | Created by: {template.create_by}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="list row">
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection
|
||||
transRoutes={inboundRoutes.map(route => ({...route, id: route._id}))}
|
||||
sectionName="Inbound Routes"
|
||||
drivers={drivers}
|
||||
vehicles={vehicles}
|
||||
onRouteClick={handleRouteClick}
|
||||
isTemplate={true}
|
||||
templateId={params.id}
|
||||
canAddNew={true}
|
||||
addText="+Add Route"
|
||||
redirect={goToCreateRoute}
|
||||
routeType="inbound"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection
|
||||
transRoutes={outboundRoutes.map(route => ({...route, id: route._id}))}
|
||||
sectionName="Outbound Routes"
|
||||
drivers={drivers}
|
||||
vehicles={vehicles}
|
||||
onRouteClick={handleRouteClick}
|
||||
isTemplate={true}
|
||||
templateId={params.id}
|
||||
canAddNew={true}
|
||||
addText="+Add Route"
|
||||
redirect={goToCreateRoute}
|
||||
routeType="outbound"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal show={showEditNameModal} onHide={closeEditNameModal}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Edit Template Name</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Template Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={closeEditNameModal} disabled={saving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={saveName} disabled={saving}>
|
||||
{saving ? <><Spinner size="sm" className="me-2" />Saving...</> : 'Save'}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
||||
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Delete Template</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
Are you sure you want to delete the template "{template?.name}"? This action cannot be undone.
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => setShowDeleteModal(false)} disabled={deleting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" onClick={deleteTemplate} disabled={deleting}>
|
||||
{deleting ? <><Spinner size="sm" className="me-2" />Deleting...</> : 'Delete'}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewDailyTemplate;
|
||||
|
||||
175
client/src/components/trans-routes/ViewDailyTemplateRoute.js
Normal file
175
client/src/components/trans-routes/ViewDailyTemplateRoute.js
Normal file
@ -0,0 +1,175 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { DailyRoutesTemplateService } from "../../services";
|
||||
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
|
||||
import { Breadcrumb, Spinner, Modal, Button } from "react-bootstrap";
|
||||
import { Pencil, Trash } from "react-bootstrap-icons";
|
||||
import RouteCustomerTable from "./RouteCustomerTable";
|
||||
|
||||
const ViewDailyTemplateRoute = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const drivers = useSelector(selectAllActiveDrivers);
|
||||
const vehicles = useSelector(selectAllActiveVehicles);
|
||||
const [template, setTemplate] = useState(null);
|
||||
const [currentRoute, setCurrentRoute] = useState(null);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
const currentVehicle = vehicles.find(item => item.id === currentRoute?.vehicle);
|
||||
const currentDriver = drivers.find(item => item.id === currentRoute?.driver);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplateAndRoute();
|
||||
}, [params.id, params.routeId]);
|
||||
|
||||
const fetchTemplateAndRoute = () => {
|
||||
DailyRoutesTemplateService.getDailyRoutesTemplate(params.id).then(data => {
|
||||
setTemplate(data.data);
|
||||
const route = data.data.routes?.find(r => r._id === params.routeId);
|
||||
setCurrentRoute(route);
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate(`/trans-routes/daily-templates/view/${params.id}`);
|
||||
};
|
||||
|
||||
const goToEdit = () => {
|
||||
navigate(`/trans-routes/daily-templates/${params.id}/update-route/${params.routeId}`);
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const deleteRoute = () => {
|
||||
setDeleting(true);
|
||||
const updatedRoutes = template.routes.filter(r => r._id !== params.routeId);
|
||||
|
||||
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
|
||||
...template,
|
||||
routes: updatedRoutes
|
||||
}).then(() => {
|
||||
setDeleting(false);
|
||||
setShowDeleteModal(false);
|
||||
navigate(`/trans-routes/daily-templates/view/${params.id}`);
|
||||
}).catch(err => {
|
||||
setDeleting(false);
|
||||
alert('Failed to delete route');
|
||||
});
|
||||
};
|
||||
|
||||
if (!template || !currentRoute) {
|
||||
return (
|
||||
<div className="list row mb-4">
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
|
||||
Daily Route Templates
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href={`/trans-routes/daily-templates/view/${params.id}`}>
|
||||
{template.name}
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>
|
||||
{currentRoute.name}
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>
|
||||
View Route in Template: {currentRoute.name}
|
||||
<button className="btn btn-link btn-sm" onClick={goBack}>Back</button>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-main-content-list-container">
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<div className="mb-3">
|
||||
<strong>Route Name:</strong> {currentRoute.name}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<strong>Type:</strong> {currentRoute.type}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<strong>Vehicle:</strong> {currentVehicle?.vehicle_number || 'Not assigned'}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<strong>Driver:</strong> {currentDriver?.name || 'Not assigned'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="mb-3">
|
||||
<strong>Start Mileage:</strong> {currentRoute.start_mileage || 'N/A'}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<strong>End Mileage:</strong> {currentRoute.end_mileage || 'N/A'}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<strong>Customers:</strong> {currentRoute.route_customer_list?.length || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<button className="btn btn-primary me-2" onClick={goToEdit}>
|
||||
<Pencil size={16} className="me-2" />
|
||||
Update Route
|
||||
</button>
|
||||
<button className="btn btn-danger" onClick={confirmDelete}>
|
||||
<Trash size={16} className="me-2" />
|
||||
Delete Route
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentRoute.route_customer_list && currentRoute.route_customer_list.length > 0 && (
|
||||
<div className="list row mt-4">
|
||||
<div className="col-md-12">
|
||||
<h5>Customer List</h5>
|
||||
<RouteCustomerTable
|
||||
transRoutes={[currentRoute]}
|
||||
sectionName={'Customers'}
|
||||
vehicles={[]}
|
||||
isTemplate={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Delete Route</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
Are you sure you want to delete the route "{currentRoute.name}" from this template?
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => setShowDeleteModal(false)} disabled={deleting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" onClick={deleteRoute} disabled={deleting}>
|
||||
{deleting ? <><Spinner size="sm" className="me-2" />Deleting...</> : 'Delete'}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewDailyTemplateRoute;
|
||||
|
||||
34
client/src/services/DailyRoutesTemplateService.js
Normal file
34
client/src/services/DailyRoutesTemplateService.js
Normal file
@ -0,0 +1,34 @@
|
||||
import http from "../http-common";
|
||||
|
||||
const getAll = (template_date) => {
|
||||
const params = {};
|
||||
if (template_date) {
|
||||
params.template_date = template_date;
|
||||
}
|
||||
return http.get("/daily-routes-templates", {params});
|
||||
};
|
||||
|
||||
const createNewDailyRoutesTemplate = (data) => {
|
||||
return http.post('/daily-routes-templates', data);
|
||||
};
|
||||
|
||||
const updateDailyRoutesTemplate = (id, data) => {
|
||||
return http.put(`/daily-routes-templates/${id}`, data);
|
||||
};
|
||||
|
||||
const getDailyRoutesTemplate = (id) => {
|
||||
return http.get(`/daily-routes-templates/${id}`);
|
||||
};
|
||||
|
||||
const deleteDailyRoutesTemplate = (id) => {
|
||||
return http.delete(`/daily-routes-templates/${id}`);
|
||||
};
|
||||
|
||||
export const DailyRoutesTemplateService = {
|
||||
getAll,
|
||||
createNewDailyRoutesTemplate,
|
||||
updateDailyRoutesTemplate,
|
||||
getDailyRoutesTemplate,
|
||||
deleteDailyRoutesTemplate
|
||||
};
|
||||
|
||||
95
client/src/shared/constants/resource.constant.js
Normal file
95
client/src/shared/constants/resource.constant.js
Normal file
@ -0,0 +1,95 @@
|
||||
// Resource Type Options
|
||||
export const RESOURCE_TYPE_OPTIONS = [
|
||||
{ value: 'doctor', label: 'Doctor' },
|
||||
{ value: 'pharmacy', label: 'Pharmacy' },
|
||||
{ value: 'hospital', label: 'Hospital' },
|
||||
{ value: 'surgical center', label: 'Surgical Center' },
|
||||
{ value: 'government agency', label: 'Government Agency' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const RESOURCE_TYPE_TEXT = {
|
||||
'doctor': 'Doctor',
|
||||
'pharmacy': 'Pharmacy',
|
||||
'hospital': 'Hospital',
|
||||
'surgical center': 'Surgical Center',
|
||||
'government agency': 'Government Agency',
|
||||
'other': 'Other',
|
||||
};
|
||||
|
||||
// Resource Specialty Options
|
||||
export const RESOURCE_SPECIALTY_OPTIONS = [
|
||||
{ value: 'Family Medicine (PCP)', label: 'Family Medicine (PCP)' },
|
||||
{ value: 'Acupuncture', label: 'Acupuncture' },
|
||||
{ value: 'Allergy & Asthma', label: 'Allergy & Asthma' },
|
||||
{ value: 'Audiology', label: 'Audiology' },
|
||||
{ value: 'Behavior Health/Social Worker', label: 'Behavior Health/Social Worker' },
|
||||
{ value: 'Botox Therapy', label: 'Botox Therapy' },
|
||||
{ value: 'Breast Surgery', label: 'Breast Surgery' },
|
||||
{ value: 'Cardiology', label: 'Cardiology' },
|
||||
{ value: 'Cardiovascular', label: 'Cardiovascular' },
|
||||
{ value: 'Colon & Rectal Surgery', label: 'Colon & Rectal Surgery' },
|
||||
{ value: 'Dentist', label: 'Dentist' },
|
||||
{ value: 'Dermatology', label: 'Dermatology' },
|
||||
{ value: 'Dialysis', label: 'Dialysis' },
|
||||
{ value: 'Endocrinology & Diabetes', label: 'Endocrinology & Diabetes' },
|
||||
{ value: 'Endodontist', label: 'Endodontist' },
|
||||
{ value: 'Endoscopy Center', label: 'Endoscopy Center' },
|
||||
{ value: 'Otolaryngology (ENT)', label: 'Otolaryngology (ENT)' },
|
||||
{ value: 'Eye Surgery Center', label: 'Eye Surgery Center' },
|
||||
{ value: 'Gastroenterology', label: 'Gastroenterology' },
|
||||
{ value: 'General Surgery', label: 'General Surgery' },
|
||||
{ value: 'GYN Oncology', label: 'GYN Oncology' },
|
||||
{ value: 'Head & Neck Surgery', label: 'Head & Neck Surgery' },
|
||||
{ value: 'Health Boutique', label: 'Health Boutique' },
|
||||
{ value: 'Hearing Aids', label: 'Hearing Aids' },
|
||||
{ value: 'Hematology & Oncology', label: 'Hematology & Oncology' },
|
||||
{ value: 'Hepatology', label: 'Hepatology' },
|
||||
{ value: 'Hospital', label: 'Hospital' },
|
||||
{ value: 'Infectious disease', label: 'Infectious disease' },
|
||||
{ value: 'Medical Center', label: 'Medical Center' },
|
||||
{ value: 'Lab', label: 'Lab' },
|
||||
{ value: 'Modified Barium Swallow (MBS) Study', label: 'Modified Barium Swallow (MBS) Study' },
|
||||
{ value: 'Medical Supply', label: 'Medical Supply' },
|
||||
{ value: 'Nephrology', label: 'Nephrology' },
|
||||
{ value: 'Neuro Surgeon', label: 'Neuro Surgeon' },
|
||||
{ value: 'Neurology', label: 'Neurology' },
|
||||
{ value: 'OB/GYN', label: 'OB/GYN' },
|
||||
{ value: 'Optometry (OD)', label: 'Optometry (OD)' },
|
||||
{ value: 'Oncology', label: 'Oncology' },
|
||||
{ value: 'Oncology Center', label: 'Oncology Center' },
|
||||
{ value: 'Ophthalmology', label: 'Ophthalmology' },
|
||||
{ value: 'Ophthalmology (Retina Specialist)', label: 'Ophthalmology (Retina Specialist)' },
|
||||
{ value: 'Oral Surgery', label: 'Oral Surgery' },
|
||||
{ value: 'Orthopaedic', label: 'Orthopaedic' },
|
||||
{ value: 'Osteopath', label: 'Osteopath' },
|
||||
{ value: 'Pain Management', label: 'Pain Management' },
|
||||
{ value: 'Periodontist', label: 'Periodontist' },
|
||||
{ value: 'Pharmacy', label: 'Pharmacy' },
|
||||
{ value: 'Physical Therapy', label: 'Physical Therapy' },
|
||||
{ value: 'Physical, Occupational, & Speech Therapy', label: 'Physical, Occupational, & Speech Therapy' },
|
||||
{ value: 'Podiatry', label: 'Podiatry' },
|
||||
{ value: 'Psychiatry', label: 'Psychiatry' },
|
||||
{ value: 'Pulmonology', label: 'Pulmonology' },
|
||||
{ value: 'Radiation Oncology', label: 'Radiation Oncology' },
|
||||
{ value: 'Radiology', label: 'Radiology' },
|
||||
{ value: 'Rehabilitation', label: 'Rehabilitation' },
|
||||
{ value: 'Rheumatology', label: 'Rheumatology' },
|
||||
{ value: 'Sleep Medicine', label: 'Sleep Medicine' },
|
||||
{ value: 'Substance Abuse Treatment', label: 'Substance Abuse Treatment' },
|
||||
{ value: 'Sports Medicine', label: 'Sports Medicine' },
|
||||
{ value: 'Surgery', label: 'Surgery' },
|
||||
{ value: 'Surgery Center', label: 'Surgery Center' },
|
||||
{ value: 'Thoracic Surgery', label: 'Thoracic Surgery' },
|
||||
{ value: 'Traditional Chinese Medicine', label: 'Traditional Chinese Medicine' },
|
||||
{ value: 'Urgent Care', label: 'Urgent Care' },
|
||||
{ value: 'Urogynecology', label: 'Urogynecology' },
|
||||
{ value: 'Urology', label: 'Urology' },
|
||||
{ value: 'Vascular and Vein', label: 'Vascular and Vein' },
|
||||
{ value: 'Vascular & Interventional Radiologist', label: 'Vascular & Interventional Radiologist' },
|
||||
{ value: 'Weight Loss / GYM', label: 'Weight Loss / GYM' },
|
||||
{ value: 'Wound Clinic', label: 'Wound Clinic' },
|
||||
];
|
||||
|
||||
// Legacy list format for backward compatibility
|
||||
export const RESOURCE_SPECIALTY_LIST = RESOURCE_SPECIALTY_OPTIONS.map(opt => opt.value);
|
||||
155
client/src/shared/constants/vehicle.constant.js
Normal file
155
client/src/shared/constants/vehicle.constant.js
Normal file
@ -0,0 +1,155 @@
|
||||
// Vehicle Seating Capacity Options
|
||||
export const SEATING_CAPACITY_OPTIONS = [
|
||||
{ value: '2', label: '2' },
|
||||
{ value: '3', label: '3' },
|
||||
{ value: '4', label: '4' },
|
||||
{ value: '5', label: '5' },
|
||||
{ value: '6', label: '6' },
|
||||
{ value: '10', label: '10' },
|
||||
{ value: '11', label: '11' },
|
||||
{ value: '12', label: '12' },
|
||||
{ value: '13', label: '13' },
|
||||
{ value: '14', label: '14' },
|
||||
{ value: '15', label: '15' },
|
||||
{ value: '23', label: '23' }
|
||||
];
|
||||
|
||||
// Fuel Type Options
|
||||
export const FUEL_TYPE = {
|
||||
PETROL: 'petrol',
|
||||
DIESEL: 'diesel'
|
||||
};
|
||||
|
||||
export const FUEL_TYPE_TEXT = {
|
||||
[FUEL_TYPE.PETROL]: 'Petrol',
|
||||
[FUEL_TYPE.DIESEL]: 'Diesel'
|
||||
};
|
||||
|
||||
// Title/Ownership Options
|
||||
export const VEHICLE_TITLE = {
|
||||
WORLDSHINE_CARE: 'worldshineCare',
|
||||
WORLDSHINE_INTERNATIONAL: 'worldshineInternational',
|
||||
WORLDSHINE_CLOVERLEAF: 'worldshineCloverleaf',
|
||||
OTHER: 'other'
|
||||
};
|
||||
|
||||
export const VEHICLE_TITLE_TEXT = {
|
||||
[VEHICLE_TITLE.WORLDSHINE_CARE]: 'Worldshine Care LLC',
|
||||
[VEHICLE_TITLE.WORLDSHINE_INTERNATIONAL]: 'Worldshine International LLC',
|
||||
[VEHICLE_TITLE.WORLDSHINE_CLOVERLEAF]: 'Worldshine Cloverleaf LLC',
|
||||
[VEHICLE_TITLE.OTHER]: 'Other-Please Specify'
|
||||
};
|
||||
|
||||
// Lift Equipped Options
|
||||
export const LIFT_EQUIPPED = {
|
||||
YES: 'true',
|
||||
NO: 'false'
|
||||
};
|
||||
|
||||
export const LIFT_EQUIPPED_TEXT = {
|
||||
[LIFT_EQUIPPED.YES]: 'Yes',
|
||||
[LIFT_EQUIPPED.NO]: 'No'
|
||||
};
|
||||
|
||||
// Repair Part Name Options (common vehicle parts)
|
||||
export const REPAIR_PART_NAME = {
|
||||
BRAKE_PADS: 'brakePads',
|
||||
BRAKE_ROTORS: 'brakeRotors',
|
||||
OIL_FILTER: 'oilFilter',
|
||||
AIR_FILTER: 'airFilter',
|
||||
CABIN_FILTER: 'cabinFilter',
|
||||
SPARK_PLUGS: 'sparkPlugs',
|
||||
BATTERY: 'battery',
|
||||
ALTERNATOR: 'alternator',
|
||||
STARTER_MOTOR: 'starterMotor',
|
||||
TIRES: 'tires',
|
||||
WHEEL_ALIGNMENT: 'wheelAlignment',
|
||||
SUSPENSION: 'suspension',
|
||||
SHOCKS: 'shocks',
|
||||
STRUTS: 'struts',
|
||||
TRANSMISSION_FLUID: 'transmissionFluid',
|
||||
COOLANT: 'coolant',
|
||||
POWER_STEERING_FLUID: 'powerSteeringFluid',
|
||||
WINDSHIELD_WIPERS: 'windshieldWipers',
|
||||
HEADLIGHTS: 'headlights',
|
||||
TAIL_LIGHTS: 'tailLights',
|
||||
MIRRORS: 'mirrors',
|
||||
BELTS: 'belts',
|
||||
HOSES: 'hoses',
|
||||
EXHAUST_SYSTEM: 'exhaustSystem',
|
||||
MUFFLER: 'muffler',
|
||||
CATALYTIC_CONVERTER: 'catalyticConverter',
|
||||
FUEL_PUMP: 'fuelPump',
|
||||
FUEL_FILTER: 'fuelFilter',
|
||||
WATER_PUMP: 'waterPump',
|
||||
THERMOSTAT: 'thermostat',
|
||||
RADIATOR: 'radiator',
|
||||
AC_COMPRESSOR: 'acCompressor',
|
||||
HEATER_CORE: 'heaterCore',
|
||||
CLUTCH: 'clutch',
|
||||
CV_JOINTS: 'cvJoints',
|
||||
WHEEL_BEARINGS: 'wheelBearings',
|
||||
TIE_RODS: 'tieRods',
|
||||
BALL_JOINTS: 'ballJoints',
|
||||
CONTROL_ARMS: 'controlArms',
|
||||
SWAY_BAR: 'swayBar',
|
||||
OTHER: 'other'
|
||||
};
|
||||
|
||||
export const REPAIR_PART_NAME_TEXT = {
|
||||
[REPAIR_PART_NAME.BRAKE_PADS]: 'Brake Pads',
|
||||
[REPAIR_PART_NAME.BRAKE_ROTORS]: 'Brake Rotors',
|
||||
[REPAIR_PART_NAME.OIL_FILTER]: 'Oil Filter',
|
||||
[REPAIR_PART_NAME.AIR_FILTER]: 'Air Filter',
|
||||
[REPAIR_PART_NAME.CABIN_FILTER]: 'Cabin Filter',
|
||||
[REPAIR_PART_NAME.SPARK_PLUGS]: 'Spark Plugs',
|
||||
[REPAIR_PART_NAME.BATTERY]: 'Battery',
|
||||
[REPAIR_PART_NAME.ALTERNATOR]: 'Alternator',
|
||||
[REPAIR_PART_NAME.STARTER_MOTOR]: 'Starter Motor',
|
||||
[REPAIR_PART_NAME.TIRES]: 'Tires',
|
||||
[REPAIR_PART_NAME.WHEEL_ALIGNMENT]: 'Wheel Alignment',
|
||||
[REPAIR_PART_NAME.SUSPENSION]: 'Suspension',
|
||||
[REPAIR_PART_NAME.SHOCKS]: 'Shocks',
|
||||
[REPAIR_PART_NAME.STRUTS]: 'Struts',
|
||||
[REPAIR_PART_NAME.TRANSMISSION_FLUID]: 'Transmission Fluid',
|
||||
[REPAIR_PART_NAME.COOLANT]: 'Coolant',
|
||||
[REPAIR_PART_NAME.POWER_STEERING_FLUID]: 'Power Steering Fluid',
|
||||
[REPAIR_PART_NAME.WINDSHIELD_WIPERS]: 'Windshield Wipers',
|
||||
[REPAIR_PART_NAME.HEADLIGHTS]: 'Headlights',
|
||||
[REPAIR_PART_NAME.TAIL_LIGHTS]: 'Tail Lights',
|
||||
[REPAIR_PART_NAME.MIRRORS]: 'Mirrors',
|
||||
[REPAIR_PART_NAME.BELTS]: 'Belts',
|
||||
[REPAIR_PART_NAME.HOSES]: 'Hoses',
|
||||
[REPAIR_PART_NAME.EXHAUST_SYSTEM]: 'Exhaust System',
|
||||
[REPAIR_PART_NAME.MUFFLER]: 'Muffler',
|
||||
[REPAIR_PART_NAME.CATALYTIC_CONVERTER]: 'Catalytic Converter',
|
||||
[REPAIR_PART_NAME.FUEL_PUMP]: 'Fuel Pump',
|
||||
[REPAIR_PART_NAME.FUEL_FILTER]: 'Fuel Filter',
|
||||
[REPAIR_PART_NAME.WATER_PUMP]: 'Water Pump',
|
||||
[REPAIR_PART_NAME.THERMOSTAT]: 'Thermostat',
|
||||
[REPAIR_PART_NAME.RADIATOR]: 'Radiator',
|
||||
[REPAIR_PART_NAME.AC_COMPRESSOR]: 'A/C Compressor',
|
||||
[REPAIR_PART_NAME.HEATER_CORE]: 'Heater Core',
|
||||
[REPAIR_PART_NAME.CLUTCH]: 'Clutch',
|
||||
[REPAIR_PART_NAME.CV_JOINTS]: 'CV Joints',
|
||||
[REPAIR_PART_NAME.WHEEL_BEARINGS]: 'Wheel Bearings',
|
||||
[REPAIR_PART_NAME.TIE_RODS]: 'Tie Rods',
|
||||
[REPAIR_PART_NAME.BALL_JOINTS]: 'Ball Joints',
|
||||
[REPAIR_PART_NAME.CONTROL_ARMS]: 'Control Arms',
|
||||
[REPAIR_PART_NAME.SWAY_BAR]: 'Sway Bar',
|
||||
[REPAIR_PART_NAME.OTHER]: 'Other'
|
||||
};
|
||||
|
||||
// Quantity Options (1-10)
|
||||
export const QUANTITY_OPTIONS = [
|
||||
{ value: '1', label: '1' },
|
||||
{ value: '2', label: '2' },
|
||||
{ value: '3', label: '3' },
|
||||
{ value: '4', label: '4' },
|
||||
{ value: '5', label: '5' },
|
||||
{ value: '6', label: '6' },
|
||||
{ value: '7', label: '7' },
|
||||
{ value: '8', label: '8' },
|
||||
{ value: '9', label: '9' },
|
||||
{ value: '10', label: '10' }
|
||||
];
|
||||
Loading…
x
Reference in New Issue
Block a user