More update for Vehicle

This commit is contained in:
Yang Li 2025-06-08 18:34:14 -04:00
parent e70fb22ac3
commit f18f213f6b
34 changed files with 1504 additions and 700 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

View File

@ -167,7 +167,14 @@ exports.updateRouteInProgress = (req, res) => {
finalCustomerList.push(cust);
}
}
const finalBody = Object.assign({}, routeBody, {route_customer_list: finalCustomerList})
let finalBody = {};
if (finalCustomerList?.length > 0) {
finalBody = Object.assign({}, routeBody, {route_customer_list: finalCustomerList})
} else {
const newBody = Object.assign({}, routeBody);
delete newBody.route_customer_list;
finalBody = Object.assign({}, newBody)
}
RoutePath.findByIdAndUpdate(currentRoute.id, finalBody, { useFindAndModify: false })
.then(data => {
// console.log('success', data.id);

View File

@ -1,10 +1,19 @@
const upload = require("../middlewares/upload");
// const uploadPhysical = require("../middlewares/upload_physical");
const dbConfig = require("../config/db.config");
const MongoClient = require("mongodb").MongoClient;
const GridFSBucket = require("mongodb").GridFSBucket;
const util = require("util");
var fs = require('fs');
var multer = require('multer');
const path = require("path");
const url = dbConfig.url;
const baseUrl = dbConfig.fileUrl;
const mongoClient = new MongoClient(url);
const readdir = util.promisify(fs.readdir);
const stat = util.promisify(fs.stat);
const uploadFiles = async (req, res) => {
try {
await upload(req, res);
@ -63,8 +72,91 @@ const deleteFile = async (req, res) => {
}
};
const uploadPhysicalFile = async (req, res) => {
try {
// Destination and file name would be adjusted to be the same with original file system
const {objectId, name, fileType, model} = req.query;
const uploadedFile = req.file;
if (!uploadedFile) {
return res.status(400).send({
message: 'No file uploaded.'
})
}
return res.status(200).send({
message: `File ${fileType} Uploaded Successfully for ${model} ${objectId}-${name}`
})
} catch (error) {
return res.status(500).send({
message: error.message,
});
}
}
const getFilesByType = async (req, res) => {
try {
const {objectId, fileType, name, model} = req.params;
if (!objectId || !name || !fileType || !model) {
return res.status(400).send({message: 'Required fields missed'});
}
const BASE_UPLOAD_DIR = `/www/wwwroot/upload/`;
const filesDir = `${BASE_UPLOAD_DIR}${model}/${objectId}/${fileType}/`;
if (!fs.existsSync(filesDir)) {
return res.status(200).send({
data: {
objectId,
model,
fileType,
name,
files: [],
count: 0
}
})
}
const fileEntries = await readdir(filesDir, { withFileTypes: true});
const filePromises = fileEntries.filter(entry => entry.isFile()).map(async (entry) => {
const filePath = path.join(filesDir, entry.name);
const stats = await stat(filePath);
const fileUrl = `/files/${model}/${objectId}/${fileType}/${entry.name}`;
return {
name: entry.name,
url: fileUrl,
size: stats.size,
extension: path.extname(entry.name),
createdAt: stats.birthtime,
modifiedAt: stats.mtime
}
});
const files = await Promise.all(filePromises);
return res.status(200).json({
data: {
objectId,
fileType,
name,
files,
count: files.length
}
})
} catch(err) {
return res.status(500).send({
message: err.message
})
}
}
module.exports = {
uploadFiles,
getFile,
deleteFile
deleteFile,
uploadPhysicalFile,
getFilesByType
};

View File

@ -21,7 +21,15 @@ exports.createVehicle = (req, res) => {
year: req.body.year || '',
checklist: req.body.checklist || '',
status: 'active',
site
site,
has_lift_equip: req.body.has_lift_equip,
vin: req.body.vin || '',
note: req.body.note || '',
insurance_expire_on: req.body.insurance_expire_on,
title_registration_on: req.body.title_registration_on,
emission_test_on: req.body.emission_test_on,
oil_change_mileage: req.body.oil_change_mileage,
oil_change_date: req.body.oil_change_date
});
// Save Vehicle in the database

View File

@ -1,6 +1,8 @@
const authJwt = require("./authJwt");
const splitSite = require("./splitSite");
const uploadPhysicalFile = require("./upload_physical");
module.exports = {
authJwt,
splitSite
splitSite,
uploadPhysicalFile
};

View File

@ -0,0 +1,93 @@
var fs = require('fs');
var multer = require('multer');
const path = require("path");
const BASE_UPLOAD_DIR = `/www/wwwroot/upload/`;
// try {
// if (!fs.existsSync(BASE_UPLOAD_DIR)) {
// fs.mkdirSync(BASE_UPLOAD_DIR, {recursive: true});
// console.log (`Created upload directory: ${BASE_UPLOAD_DIR}`);
// }
// } catch(err) {
// console.error(`Error creating directory ${BASE_UPLOAD_DIR}`)
// }
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const { objectId, fileType, name, model } = req.query;
if (!objectId || !name || !fileType || !model) {
return res.status(400).send({message: 'Required fields missed'});
}
const uploadDir = `${BASE_UPLOAD_DIR}${model}/${objectId}/${fileType}/`;
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, {recursive: true});
console.log (`Created upload directory: ${uploadDir}/`);
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const { objectId, fileType, name, date} = req.query;
const uniqueSuffix = date || Date.now();
const payload = req.body;
console.log('is the payload', payload);
cb(null, `${name.replaceAll(' ', '_')}_${objectId}_${fileType}_${uniqueSuffix}${path.extname(file.originalname)}`)
}
})
const uploadPhysicalFile = multer({
storage: storage,
limits: {fileSize: 10 * 1024* 1024},
fileFilter: (req, file, cb) => cb(null, true)
}).single('file');
// const memoryStorage = multer.memoryStorage();
// const upload = multer({
// storage: memoryStorage,
// limits: { fileSize: 10 * 1024* 1024}
// });
// const uploadPhysicalFile = (req, res, next) => {
// upload.single('file')(req, res, async (err) => {
// if (err) {
// return res.status(400).send({message: `Uplodad Error: ${err}`});
// }
// if (!req.file) {
// return res.status(400).send({message: 'No File uploaded'});
// }
// try {
// const {objectId, name, fileType} = req.body;
// if (!objectId || !name || fileType) {
// return res.status(400).send({message: 'Required fields missed'});
// }
// const uploadDir = `${BASE_UPLOAD_DIR}${objectId}/${fileType}/`;
// if (!fs.existsSync(uploadDir)) {
// fs.mkdirSync(uploadDir, {recursive: true});
// console.log (`Created upload directory: ${uploadDir}`);
// }
// const uniqueSuffix = Date.now();
// const fileExension = path.extname(req.file.originalname);
// const fileName = `${name.replaceAll(' ', '_')}_${objectId}_${fileType}_${uniqueSuffix}${fileExension}`;
// const filePath = path.join(uploadDir, fileName);
// fs.writeFileSync(filePath, req.file.buffer);
// req.fileInfo = {
// filename: fileName,
// path: filePath,
// originalname: req.file.originalname,
// size: req.file.size
// };
// next();
// } catch (err) {
// return res.status(500).send({message: `Upload Parsing error: ${err}`});
// }
// })
// }
module.exports = uploadPhysicalFile;

View File

@ -1,4 +1,3 @@
const uniqueValidator = require('mongoose-unique-validator');
module.exports = mongoose => {
var schema = mongoose.Schema(
{

View File

@ -25,7 +25,15 @@ module.exports = mongoose => {
type: String
}],
status: String,
site: Number
site: Number,
vin: String,
has_lift_equip: Boolean,
insurance_expire_on: String,
title_registration_on: String,
emission_test_on: String,
oil_change_mileage: Number,
oil_change_date: String,
note: String
},
{ collection: 'vehicle', timestamps: true }
);

View File

@ -1,8 +1,12 @@
const {uploadPhysicalFile} = require("../middlewares");
module.exports = app => {
const upload = require("../controllers/upload.controller.js");
var router = require("express").Router();
router.get("/:name", upload.getFile);
router.post("/upload/:filename", upload.uploadFiles);
router.post("/upload-physical", [uploadPhysicalFile], upload.uploadPhysicalFile);
router.post("/delete", upload.deleteFile);
router.get("/uploadedDocs/:model/:objectId/type/:fileType/name/:name", upload.getFilesByType);
app.use('/api/files', router);
};

View File

@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.2fa2d232.css",
"main.js": "/static/js/main.da419ea1.js",
"main.css": "/static/css/main.e5412702.css",
"main.js": "/static/js/main.26b7d753.js",
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
"index.html": "/index.html",
"main.2fa2d232.css.map": "/static/css/main.2fa2d232.css.map",
"main.da419ea1.js.map": "/static/js/main.da419ea1.js.map",
"main.e5412702.css.map": "/static/css/main.e5412702.css.map",
"main.26b7d753.js.map": "/static/js/main.26b7d753.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
},
"entrypoints": [
"static/css/main.2fa2d232.css",
"static/js/main.da419ea1.js"
"static/css/main.e5412702.css",
"static/js/main.26b7d753.js"
]
}

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,126 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,126 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* 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

File diff suppressed because one or more lines are too long

737
client/package-lock.json generated
View File

@ -8,11 +8,14 @@
"name": "client",
"version": "0.1.0",
"dependencies": {
"@aldabil/react-scheduler": "^2.9.2",
"@babel/polyfill": "^7.12.1",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@reduxjs/toolkit": "^1.8.1",
"@schedule-x/event-modal": "^2.30.0",
"@schedule-x/events-service": "^2.30.0",
"@schedule-x/react": "^2.30.0",
"@schedule-x/theme-default": "^2.30.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
@ -53,18 +56,6 @@
"tape": "^5.5.3"
}
},
"node_modules/@aldabil/react-scheduler": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@aldabil/react-scheduler/-/react-scheduler-2.9.2.tgz",
"integrity": "sha512-WederN8Pmhg7nQdT7685UITFUPk/FDjMC0bSuyeqgHZSn2nhVMqiLVFC4q0kHh+v75lQCPqkFKr8FBnEoZNRig==",
"peerDependencies": {
"@mui/icons-material": ">=5.0.0",
"@mui/material": ">=5.0.0",
"@mui/x-date-pickers": ">=6.19.0",
"date-fns": ">=3.2.0",
"react": ">=17.0.0"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@ -2352,44 +2343,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.1.tgz",
"integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==",
"peer": true,
"dependencies": {
"@floating-ui/utils": "^0.2.0"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz",
"integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
"peer": true,
"dependencies": {
"@floating-ui/core": "^1.0.0",
"@floating-ui/utils": "^0.2.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz",
"integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==",
"peer": true,
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
"integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==",
"peer": true
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@ -3118,338 +3071,6 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz",
"integrity": "sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg=="
},
"node_modules/@mui/base": {
"version": "5.0.0-beta.40",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
"integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9",
"@floating-ui/react-dom": "^2.0.8",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"@popperjs/core": "^2.11.8",
"clsx": "^2.1.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "5.15.16",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.16.tgz",
"integrity": "sha512-PTIbMJs5C/vYMfyJNW8ArOezh4eyHkg2pTeA7bBxh2kLP1Uzs0Nm+krXWbWGJPwTWjM8EhnDrr4aCF26+2oleg==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/icons-material": {
"version": "5.15.16",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.16.tgz",
"integrity": "sha512-s8vYbyACzTNZRKv+20fCfVXJwJqNcVotns2EKnu1wmAga6wv2LAo5kB1d5yqQqZlMFtp34EJvRXf7cy8X0tJVA==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^5.0.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material": {
"version": "5.15.16",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.16.tgz",
"integrity": "sha512-ery2hFReewko9gpDBqOr2VmXwQG9ifXofPhGzIx09/b9JqCQC/06kZXZDGGrOTpIddK9HlIf4yrS+G70jPAzUQ==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/base": "5.0.0-beta.40",
"@mui/core-downloads-tracker": "^5.15.16",
"@mui/system": "^5.15.15",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^18.2.0",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"peer": true
},
"node_modules/@mui/private-theming": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
"integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/utils": "^5.15.14",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/styled-engine": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9",
"@emotion/cache": "^11.11.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/system": {
"version": "5.15.15",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
"integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/private-theming": "^5.15.14",
"@mui/styled-engine": "^5.15.14",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/types": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
"peer": true,
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/utils": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
"integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.9",
"@types/prop-types": "^15.7.11",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/utils/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"peer": true
},
"node_modules/@mui/x-date-pickers": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.3.2.tgz",
"integrity": "sha512-i7JaDs1eXSZWyJihfszUHVV0t/C2HvtdMv5tHwv3E3enMx5Hup1vkJ64vZAH2fgGrTHQH8mjxvVsmI6jhDXIUg==",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.24.0",
"@mui/base": "^5.0.0-beta.40",
"@mui/system": "^5.15.14",
"@mui/utils": "^5.15.14",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.0",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14",
"date-fns": "^2.25.0 || ^3.2.0",
"date-fns-jalali": "^2.13.0-0",
"dayjs": "^1.10.7",
"luxon": "^3.0.2",
"moment": "^2.29.4",
"moment-hijri": "^2.1.2",
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"date-fns": {
"optional": true
},
"date-fns-jalali": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
},
"moment-hijri": {
"optional": true
},
"moment-jalaali": {
"optional": true
}
}
},
"node_modules/@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.3",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
@ -3547,6 +3168,32 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@preact/signals": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@preact/signals/-/signals-2.0.4.tgz",
"integrity": "sha512-9241aGnIv7y0IGzaq2vkBMe8/0jGnnmEEUeFmAoTWsaj8q/BW2PVekL8nHVJcy69bBww6rwEy3A1tc6yPE0sJA==",
"peer": true,
"dependencies": {
"@preact/signals-core": "^1.7.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
},
"peerDependencies": {
"preact": ">= 10.25.0"
}
},
"node_modules/@preact/signals-core": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz",
"integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
@ -3753,6 +3400,45 @@
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw=="
},
"node_modules/@schedule-x/calendar": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/calendar/-/calendar-2.30.0.tgz",
"integrity": "sha512-+yAVnY+4T8+iTrVp03V3usVYKhLuBzWEEtYOKsTnbFapD3LaL02Rv+0Gmv+4Vpk1H5yd3znrSxQvDLNBVNmUhg==",
"peer": true,
"peerDependencies": {
"@preact/signals": "^2.0.2",
"preact": "^10.19.2"
}
},
"node_modules/@schedule-x/event-modal": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/event-modal/-/event-modal-2.30.0.tgz",
"integrity": "sha512-RS/p5/rg/wCooKR5y05orJBJjxf7Ls7/VcGsz5MemJCQCXhu5pbU0g+OVVSlh6tJ+OoKF5frm4lIhXu82gd6/Q==",
"peerDependencies": {
"@preact/signals": "^2.0.2",
"preact": "^10.19.2"
}
},
"node_modules/@schedule-x/events-service": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/events-service/-/events-service-2.30.0.tgz",
"integrity": "sha512-WztKcKSj9uqvjvVr5bVe5tAzR4FKDBPQ/FFnuDWE2Y76yrxs7Hy4RROG/UCmteGrzJeXv3BV+TU2l63scJUADA=="
},
"node_modules/@schedule-x/react": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/react/-/react-2.30.0.tgz",
"integrity": "sha512-9eVcoE7xYDYHnS+2JwgtRoEZtfzoJdfNHb+VMgvgJ0Ur+ItGifN5DyrFlyQ3l18E848ny0+wp1J8qRvlV5ikMw==",
"peerDependencies": {
"@schedule-x/calendar": "^2.25.0",
"react": "^16.7.0 || ^17 || ^18 || ^19",
"react-dom": "^16.7.0 || ^17 || ^18 || ^19"
}
},
"node_modules/@schedule-x/theme-default": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/theme-default/-/theme-default-2.30.0.tgz",
"integrity": "sha512-vRUDV3fg7SoETiWQktv3DwYKjFZQMHwuYw+VKW5T3dzEJ8YfatuJbg0sj0JCiu9XQYcjs0hdmzrQvIDFy93r/w=="
},
"node_modules/@sinclair/typebox": {
"version": "0.23.5",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz",
@ -7026,15 +6712,6 @@
"wrap-ansi": "^7.0.0"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -7945,23 +7622,6 @@
"node": ">=10"
}
},
"node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/dayjs": {
"version": "1.11.11",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
"integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==",
"optional": true,
"peer": true
},
"node_modules/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
@ -16991,6 +16651,16 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/preact": {
"version": "10.26.6",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz",
"integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -22218,12 +21888,6 @@
}
},
"dependencies": {
"@aldabil/react-scheduler": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@aldabil/react-scheduler/-/react-scheduler-2.9.2.tgz",
"integrity": "sha512-WederN8Pmhg7nQdT7685UITFUPk/FDjMC0bSuyeqgHZSn2nhVMqiLVFC4q0kHh+v75lQCPqkFKr8FBnEoZNRig==",
"requires": {}
},
"@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@ -23777,40 +23441,6 @@
}
}
},
"@floating-ui/core": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.1.tgz",
"integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==",
"peer": true,
"requires": {
"@floating-ui/utils": "^0.2.0"
}
},
"@floating-ui/dom": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz",
"integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
"peer": true,
"requires": {
"@floating-ui/core": "^1.0.0",
"@floating-ui/utils": "^0.2.0"
}
},
"@floating-ui/react-dom": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz",
"integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==",
"peer": true,
"requires": {
"@floating-ui/dom": "^1.0.0"
}
},
"@floating-ui/utils": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
"integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==",
"peer": true
},
"@humanwhocodes/config-array": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@ -24351,146 +23981,6 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz",
"integrity": "sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg=="
},
"@mui/base": {
"version": "5.0.0-beta.40",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
"integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9",
"@floating-ui/react-dom": "^2.0.8",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"@popperjs/core": "^2.11.8",
"clsx": "^2.1.0",
"prop-types": "^15.8.1"
}
},
"@mui/core-downloads-tracker": {
"version": "5.15.16",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.16.tgz",
"integrity": "sha512-PTIbMJs5C/vYMfyJNW8ArOezh4eyHkg2pTeA7bBxh2kLP1Uzs0Nm+krXWbWGJPwTWjM8EhnDrr4aCF26+2oleg==",
"peer": true
},
"@mui/icons-material": {
"version": "5.15.16",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.16.tgz",
"integrity": "sha512-s8vYbyACzTNZRKv+20fCfVXJwJqNcVotns2EKnu1wmAga6wv2LAo5kB1d5yqQqZlMFtp34EJvRXf7cy8X0tJVA==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9"
}
},
"@mui/material": {
"version": "5.15.16",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.16.tgz",
"integrity": "sha512-ery2hFReewko9gpDBqOr2VmXwQG9ifXofPhGzIx09/b9JqCQC/06kZXZDGGrOTpIddK9HlIf4yrS+G70jPAzUQ==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9",
"@mui/base": "5.0.0-beta.40",
"@mui/core-downloads-tracker": "^5.15.16",
"@mui/system": "^5.15.15",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^18.2.0",
"react-transition-group": "^4.4.5"
},
"dependencies": {
"react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"peer": true
}
}
},
"@mui/private-theming": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
"integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9",
"@mui/utils": "^5.15.14",
"prop-types": "^15.8.1"
}
},
"@mui/styled-engine": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9",
"@emotion/cache": "^11.11.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
}
},
"@mui/system": {
"version": "5.15.15",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
"integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9",
"@mui/private-theming": "^5.15.14",
"@mui/styled-engine": "^5.15.14",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
}
},
"@mui/types": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
"peer": true,
"requires": {}
},
"@mui/utils": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
"integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
"peer": true,
"requires": {
"@babel/runtime": "^7.23.9",
"@types/prop-types": "^15.7.11",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
},
"dependencies": {
"react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"peer": true
}
}
},
"@mui/x-date-pickers": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.3.2.tgz",
"integrity": "sha512-i7JaDs1eXSZWyJihfszUHVV0t/C2HvtdMv5tHwv3E3enMx5Hup1vkJ64vZAH2fgGrTHQH8mjxvVsmI6jhDXIUg==",
"peer": true,
"requires": {
"@babel/runtime": "^7.24.0",
"@mui/base": "^5.0.0-beta.40",
"@mui/system": "^5.15.14",
"@mui/utils": "^5.15.14",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.0",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
}
},
"@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.3",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
@ -24542,6 +24032,21 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
},
"@preact/signals": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@preact/signals/-/signals-2.0.4.tgz",
"integrity": "sha512-9241aGnIv7y0IGzaq2vkBMe8/0jGnnmEEUeFmAoTWsaj8q/BW2PVekL8nHVJcy69bBww6rwEy3A1tc6yPE0sJA==",
"peer": true,
"requires": {
"@preact/signals-core": "^1.7.0"
}
},
"@preact/signals-core": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz",
"integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==",
"peer": true
},
"@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
@ -24702,6 +24207,35 @@
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw=="
},
"@schedule-x/calendar": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/calendar/-/calendar-2.30.0.tgz",
"integrity": "sha512-+yAVnY+4T8+iTrVp03V3usVYKhLuBzWEEtYOKsTnbFapD3LaL02Rv+0Gmv+4Vpk1H5yd3znrSxQvDLNBVNmUhg==",
"peer": true,
"requires": {}
},
"@schedule-x/event-modal": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/event-modal/-/event-modal-2.30.0.tgz",
"integrity": "sha512-RS/p5/rg/wCooKR5y05orJBJjxf7Ls7/VcGsz5MemJCQCXhu5pbU0g+OVVSlh6tJ+OoKF5frm4lIhXu82gd6/Q==",
"requires": {}
},
"@schedule-x/events-service": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/events-service/-/events-service-2.30.0.tgz",
"integrity": "sha512-WztKcKSj9uqvjvVr5bVe5tAzR4FKDBPQ/FFnuDWE2Y76yrxs7Hy4RROG/UCmteGrzJeXv3BV+TU2l63scJUADA=="
},
"@schedule-x/react": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/react/-/react-2.30.0.tgz",
"integrity": "sha512-9eVcoE7xYDYHnS+2JwgtRoEZtfzoJdfNHb+VMgvgJ0Ur+ItGifN5DyrFlyQ3l18E848ny0+wp1J8qRvlV5ikMw==",
"requires": {}
},
"@schedule-x/theme-default": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/@schedule-x/theme-default/-/theme-default-2.30.0.tgz",
"integrity": "sha512-vRUDV3fg7SoETiWQktv3DwYKjFZQMHwuYw+VKW5T3dzEJ8YfatuJbg0sj0JCiu9XQYcjs0hdmzrQvIDFy93r/w=="
},
"@sinclair/typebox": {
"version": "0.23.5",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz",
@ -27274,12 +26808,6 @@
"wrap-ansi": "^7.0.0"
}
},
"clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"peer": true
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -27974,19 +27502,6 @@
"whatwg-url": "^8.0.0"
}
},
"date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"peer": true
},
"dayjs": {
"version": "1.11.11",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
"integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==",
"optional": true,
"peer": true
},
"debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
@ -34541,6 +34056,12 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"preact": {
"version": "10.26.6",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz",
"integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==",
"peer": true
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -3,11 +3,14 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@aldabil/react-scheduler": "^2.9.2",
"@babel/polyfill": "^7.12.1",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@reduxjs/toolkit": "^1.8.1",
"@schedule-x/event-modal": "^2.30.0",
"@schedule-x/events-service": "^2.30.0",
"@schedule-x/react": "^2.30.0",
"@schedule-x/theme-default": "^2.30.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",

View File

@ -660,11 +660,107 @@ input[type="checkbox"] {
padding-right: 8px;
}
.sx__month-grid-day {
min-height: 150px;
}
.sx__event-modal {
box-shadow: 1px -1px 20px rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
.sx__event-modal__title {
padding: 8px;
}
.sx__event-modal__time {
padding: 0 8px 8px 8px;
}
.sx__event-modal__time.with-padding {
padding-left: 8px;
}
.event-item-flex {
display: flex;
justify-content: space-between;
padding: 4px 8px;
}
.event-list-item-container {
border-radius: 8px;
}
.event-red {
border-inline-start: 4px solid rgb(249, 28, 69) !important;
color: rgb(89, 0, 13) !important;
background-color: rgb(255, 210, 220) !important;
}
.event-brown {
border-inline-start: 4px solid #d0b316 !important;
color: #594800 !important;
background-color: #fff5aa !important;
}
.event-green {
border-inline-start: 4px solid #004d3d !important;
color: #004d3d !important;
background-color: #dafff0 !important;
}
.event-blue {
border-inline-start: 4px solid rgb(28, 125, 249) !important;
color: rgb(0, 40, 89) !important;
background-color: rgb(210, 231, 255) !important;
}
.event-black {
border-inline-start: 4px solid black !important;
color: black !important;
background-color: #aaa !important;
}
.event-purple {
border-inline-start: 4px solid #6750a4 !important;
color: #21005e !important;
background-color: #eaddff !important;
}
.event-primary {
border-inline-start: 4px solid #6750a4 !important;
color: #21005e !important;
background-color: #eaddff !important;
}
.event-gray {
border-inline-start: 4px solid #777 !important;
color: #555 !important;
background-color: #eee !important;
}
.event-orange {
border-inline-start: 4px solid #F76806 !important;
color: #C35214 !important;
background-color: #FED8B1 !important;
}
.event-pink {
border-inline-start: 4px solid #FF6EC7 !important;
color: #A94064 !important;
background-color: #FFD1DC !important;
}
.btn-no-deco {
text-decoration: none!important;
padding-top: 6px;
}
.small-dropdown-item {
font-size: 12px;
color: #555;
}
.personnel-info-table .red {
background: #dc3545;
color: rgb(25, 23, 23);

View File

@ -38,6 +38,7 @@ const UpdateEmployee = () => {
const [status, setStatus] = useState('');
const [tags, setTags] = useState('');
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedFile, setSelectedFile] = useState();
const params = new URLSearchParams(window.location.search);
const redirectTo = () => {
const redirect = params.get('redirect');
@ -186,6 +187,16 @@ const UpdateEmployee = () => {
}
const formData = new FormData();
formData.append("file", selectedFile);
// formData.append('employeeId', currentEmployee?.id);
// formData.append('name', `${currentEmployee?.lastname}_${currentEmployee?.firstname}`);
// formData.append('fileType', 'I9');
if (selectedFile) {
EmployeeService.uploadEmployeeFile(formData, currentEmployee?.id, `${currentEmployee?.lastname}_${currentEmployee?.firstname}`, 'I9');
}
if (params.get('type') === 'driver') {
dispatch(updateDriver({id: urlParams.id, data, currentEmployee}));
} else {
@ -288,6 +299,13 @@ const UpdateEmployee = () => {
<div className="col-md-4 mb-4">
<div>Tags(Please use ',' between each tags):</div> <input type="text" value={tags || ''} onChange={e => setTags(e.target.value)}/>
</div>
<div className="col-md-4 mb-4">
<div>Upload I9:</div>
<input
type="file"
onChange={(e) => setSelectedFile(e.target.files[0])}
/>
</div>
</div>
<div className="list row mb-5">
<div className="col-md-6 col-sm-6 col-xs-12">

View File

@ -9,6 +9,7 @@ const ViewEmployee = () => {
const urlParams = useParams();
const [currentEmployee, setCurrentEmployee] = useState(undefined);
const [i9Files, setI9Files] = useState([]);
const redirectTo = () => {
navigate(`/employees/list`)
@ -26,6 +27,12 @@ const ViewEmployee = () => {
})
}
}, []);
useEffect(() => {
if (currentEmployee) {
EmployeeService.getAllEmployeeFiles(currentEmployee?.id, currentEmployee?.name, 'I9').then(data => { setI9Files(data?.data?.data?.files)} )
}
}, [currentEmployee])
return (
<>
@ -92,6 +99,14 @@ const ViewEmployee = () => {
<div className="col-md-4 mb-4">
<div>Tags: {currentEmployee?.tags?.join(', ')}</div>
</div>
<div className="col-md-4 mb-4">
<div>I9: </div>
<div>
{
i9Files?.map(item => <div><a href={`${window.location.origin}${item?.url}`} target="_blank">{item?.name}</a></div> )
}
</div>
</div>
</div>
</>
);

View File

@ -2,8 +2,23 @@ import React, {useState, useEffect} from "react";
import { useNavigate } from "react-router-dom";
import { AuthService, EventsService, CustomerService, ResourceService } from "../../services";
import moment from 'moment';
import { Button, Modal } from "react-bootstrap";
import { Scheduler } from "@aldabil/react-scheduler";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Button, Modal, Dropdown } from "react-bootstrap";
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
import {
viewWeek,
viewDay,
viewMonthGrid,
viewMonthAgenda,
createViewDay,
createViewMonthAgenda,
createViewWeek,
createViewMonthGrid
} from '@schedule-x/calendar';
import { createEventsServicePlugin } from '@schedule-x/events-service';
import { createEventModalPlugin} from '@schedule-x/event-modal';
import '@schedule-x/theme-default/dist/calendar.css';
import { Archive, PencilSquare, Filter } from "react-bootstrap-icons";
// import { Scheduler } from "@aldabil/react-scheduler";
const EventsCalendar = () => {
@ -18,6 +33,43 @@ const EventsCalendar = () => {
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 [groupedEvents, setGroupedEvents] = useState(new Map());
const calendar = useCalendarApp({
views: [createViewMonthGrid(), createViewDay(), createViewMonthAgenda()],
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]
})
const getGroupedEvents = () => {
const eventsDateMap = new Map();
console.log('events', events);
for (const eventItem of events) {
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);
}
}
console.log('eventsMap', eventsDateMap);
return eventsDateMap;
}
useEffect(() => {
if (!AuthService.canAccessLegacySystem()) {
@ -52,15 +104,17 @@ const EventsCalendar = () => {
item.newPatient = item?.data?.new_patient || '';
item.needId = item?.data?.need_id || '';
item.disability = item?.data?.disability || '';
item.startTime = item?.start_time? `${new Date(item?.start_time).toLocaleDateString()} ${moment(new Date(item?.start_time)).format('hh:mm A')}` : '' ;
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('hh:mm A')}` : '' ;
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('hh:mm A')}` : '' ;
item.fasting = item?.data?.fasting || '';
item.transportation = item?.link_event_name || '';
item.title = `${moment(new Date(item?.start_time)).format('hh:mm A') || ''} ${doctorField} / ${customerField}`;
item.start = new Date(item.start_time);
item.end = item.stop_time? new Date(item.stop_time): new Date(item.start_time);
item.title = `${customerField}, provider: ${doctorField}`;
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '';
item.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : '';
const transportationInfo = EventsService.getTransportationInfo(data.data, 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;
@ -73,10 +127,17 @@ const EventsCalendar = () => {
setCurrentTotalResource(item.totalResource);
return item;
}).filter(item => item.type === 'medical'));
})}
}, [fromDate, toDate, customers, resources, timeData]);
useEffect(() => {
if (events) {
events?.forEach((item) => calendar.eventsService.add(item));
setGroupedEvents(getGroupedEvents());
}
}, [events]);
const redirectToAdmin = () => {
navigate(`/medical`)
}
@ -102,6 +163,8 @@ const EventsCalendar = () => {
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,
@ -121,12 +184,14 @@ const EventsCalendar = () => {
item.newPatient = item?.data?.new_patient || '';
item.needId = item?.data?.need_id || '';
item.disability = item?.data?.disability || '';
item.startTime = item?.start_time? `${new Date(item?.start_time).toLocaleDateString()} ${moment(new Date(item?.start_time)).format('hh:mm A')}` : '' ;
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('hh:mm A')}` : '' ;
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('hh:mm A')}` : '' ;
item.fasting = item?.data?.fasting || '';
item.transportation = item?.link_event_name || '';
item.title = `${moment(new Date(item?.start_time)).format('hh:mm A') || ''} ${doctorField} / ${customerField}`;
item.title = `${customerField}, provider: ${doctorField}`;
item.start = new Date(item.start_time);
item.end = item.stop_time? new Date(item.stop_time): new Date(item.start_time);
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;
@ -145,52 +210,121 @@ const EventsCalendar = () => {
})
});
}
const FilterAndClose = () => {
setShowFilterDropdown(false);
}
const cleanFilterAndClose = () => {
setShowFilterDropdown(false);
setShowDeletedItems(false);
}
const customComponents = {
eventModal: ({calendarEvent}) => {
return <>
<div className="sx__event-modal__title">{calendarEvent?.customer}</div>
<div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</div>
<div className="sx__event-modal__time">{`${calendarEvent?.startTime}`}</div>
<div className="sx__event-modal__time">
<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>
);
},
);
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item>Medical</Breadcrumb.Item>
<Breadcrumb.Item active>
Medical Event Calendar
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h5>Medical Event Calendar <button className="btn btn-link btn-sm" onClick={() => {redirectToAdmin()}}>Back</button></h5>
<h4>
Medical Event Calendar
</h4>
</div>
</div>
<div className="list row mb-4">
<div className="col-md-12 col-sm-12 mb-4">
Show Deleted Events: <input type="checkbox" value={showDeletedItems} checked={showDeletedItems === true} onClick={() => setShowDeletedItems(!showDeletedItems)} />
<div className="app-main-content-list-container" style={{"min-width": "1500px"}}>
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="eventsCalendar" id="events-calendar-tab">
<Tab eventKey="eventsCalendar" title="Medical Appointments">
<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">
<h6 className="text-primary me-4">List</h6>
{
Array.from(groupedEvents?.keys())?.map((key) => {
return <>
<h6 className="text-primary me-2">{key}</h6>
{
groupedEvents.get(key).map(eventItem => <div className={`event-${eventItem.color || 'primary'} mb-4 event-list-item-container`}>
<div className="event-item-flex">
<div className="sx__month-agenda-event__title">{eventItem.customer}</div>
<div className="sx__event-modal__time">{`${moment(eventItem?.start_time).format('hh:mm A')} ${eventItem?.end_time ? `- ${moment(eventItem?.end_time).format('hh:mm A')}` : '' }`}</div>
</div>
<div className="sx__event-modal__time with-padding">{`provider: ${eventItem?.doctor}`}</div>
</div>)
}
</>
})
}
</div>
</div>
</div>
</Tab>
</Tabs>
<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>
</div>
<div className="col-md-12 col-sm-12 mb-4">
<Scheduler
view="month"
events={showDeletedItems ? events : events.filter(event => event.status === 'active')}
month = {{
weekDays: [0, 1, 2, 3, 4, 5, 6],
weekStartOn: 0,
startHour: 0,
endHour: 24,
navigation: true,
disableGoToDay: false
}}
day = {{
startHour: 0,
endHour: 24,
step:1440,
navigation: true
}}
onSelectedDateChange = {(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))}}
onEventEdit = {(event) => goToEdit(event.event_id)}
onDelete = {(id) => disableEvent(id)}
viewerExtraComponent = {(fields, event) => {
return (<div>
<div>{`Client: ${event.customer}`}</div>
<div>{`Doctor: ${event.doctor}`}</div>
<div>{`Contact: ${event.contact}`} </div>
<div>{`Address: ${event.address}`} </div>
<hr />
</div>)
}}
/>
</div>
</div>
</div>
</>
)
};

View File

@ -2,11 +2,12 @@ import { Bell, ChevronDown, PersonCircle } from 'react-bootstrap-icons';
import { Outlet, useLocation, Navigate } from 'react-router-dom';
import { AuthService } from '../../services';
import SideMenu from './menu';
import { Dropdown } from "react-bootstrap";
function Layout() {
const location = useLocation();
const showMenu = location.pathname !== '/login' && location.pathname !== '/landing'; // Example: Hide menu on login page
const user = localStorage.getItem('user') || {};
const user = localStorage.getItem('user');
const getLogoSuffix = () => {
return (window.location.hostname.includes('worldshine2.mayo.llc') || window.location.hostname.includes('site2') || window.location.host.includes('ws2') ||window.location.hostname.includes('localhost')) ? "Care LLC" : ((window.location.hostname.includes('worldshine3.mayo.llc') ||window.location.hostname.includes('site3') || window.location.hostname.includes('ws3')) ? "Cloverleaf LLC" : "International LLC");
}
@ -20,12 +21,21 @@ function Layout() {
<div className="app-menu-user-profile-container">
<Bell size={16} color="#0066B1"/>
<div className="app-menu-user-profile ms-2">
<PersonCircle size={24}/>
<div className="user-info-container me-2">
<div className="user-name">{JSON.parse(user).username}</div>
<div className="user-role">{JSON.parse(user).roles[0]}</div>
</div>
<ChevronDown size={12} color="#555"></ChevronDown>
<PersonCircle size={24}/>
<div className="user-info-container me-2">
<div className="user-name">{user && JSON.parse(user)?.username}</div>
<div className="user-role">{user && JSON.parse(user)?.roles[0]}</div>
</div>
<Dropdown>
<Dropdown.Toggle variant="tertiary" id="user-basic">
{/* <ChevronDown size={12} color="#555"></ChevronDown> */}
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item className="small-dropdown-item" onClick={() => AuthService.logout()}>Logout</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
</div>

View File

@ -206,7 +206,6 @@ const SideMenu = () => {
}
const goToLink = (link) => {
console.log('this is lik', link);
navigate(link);
}

View File

@ -2,8 +2,10 @@ import React, {useEffect, useState} from "react";
import { useSelector,useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { vehicleSlice, selectVehicleError } from "./../../store";
import { AuthService } from "../../services";
import { AuthService, VehicleService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import DatePicker from "react-datepicker";
import moment from 'moment';
const CreateVehicle = () => {
const navigate = useNavigate();
@ -26,6 +28,18 @@ const CreateVehicle = () => {
const [mileage, setMileage] = useState();
const [capacity, setCapacity] = useState();
const [checklist, setChecklist] = useState(['']);
const [hasLiftEquip, setHasLiftEquip] = useState(undefined);
const [insuranceExpireOn, setInsuranceExpireOn] = useState(undefined);
const [titleRegistrationOn, setTitleRegistrationOn] = useState(undefined);
const [emissionTestOn, setEmissionTestOn] = useState(undefined);
const [oilChangeMileage, setOilChangeMileage] = useState(undefined);
const [oilChangeDate, setOilChangeDate] = useState(undefined);
const [vin, setVin] = useState('');
const [note, setNote] = useState('');
// const [selectedMothlyFile, setSelectedMonthlyFile] = useState();
// const [selectedYearlyFile, setSelectedYearlyFile] = useState();
// const [monthlyInspectionDate, setMonthlyInspectionDate] = useState();
// const [yearlyInspectionDate, setYearlyInspectionDate] = useState();
const error = useSelector(selectVehicleError);
const redirectTo = () => {
@ -60,11 +74,27 @@ const CreateVehicle = () => {
make,
vehicle_model: vehicleModel,
status: 'active',
checklist
checklist,
note,
vin,
has_lift_equip: hasLiftEquip === 'true',
insurance_expire_on: moment(insuranceExpireOn).format('MM/DD/YYYY'),
title_registration_on: moment(titleRegistrationOn).format('MM/DD/YYYY'),
emission_test_on: moment(emissionTestOn).format('MM/DD/YYYY'),
oil_change_date: moment(oilChangeDate).format('MM/DD/YYYY')
};
dispatch(createVehicle({data, redirectFun: redirectTo}));
}
// const saveDocument = () => {
// if (selectedMothlyFile && monthlyInspectionDate) {
// VehicleService.uploadVechileFile(formData, currentVechile.id, currentVehchile.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
// }
// if (selectedYearlyFile && yearlyInspectionDate) {
// VehicleService.uploadVechileFile(formData, currentVechile.id, currentVehchile.vehicle_number, 'yearlyInspection', yearlyInspectionDate);
// }
// }
return (
<>
@ -101,20 +131,57 @@ const CreateVehicle = () => {
</div>
<div className="app-main-content-fields-section">
<div className="me-4"><div className="field-label">Year</div><input type="text" value={year || ''} placeholder="e.g.,2016" onChange={e => setYear(e.target.value)}/></div>
<div className="me-4"><div className="field-label">Vin Number</div><input type="text" value={vin || ''} placeholder="e.g, 1FBAX2CM9KA34959" onChange={e => setVin(e.target.value)}/></div>
<div className="me-4"><div className="field-label">Licence Plate</div><input type="text" value={tag || ''} placeholder="e.g.,91579HT" onChange={e => setTag(e.target.value)}/></div>
<div className="me-4"><div className="field-label">GPS ID</div><input type="text" value={gps || ''} placeholder="e.g.,609671" onChange={e => setGps(e.target.value)}/></div>
<div className="me-4"><div className="field-label">EZPass</div><input type="text" value={ezpass || ''} placeholder="e.g.,NY12345" onChange={e => setEzpass(e.target.value)}/></div>
{/* Lift Equipped & VIN Number would be added later */}
<div className="me-4">
<div className="field-label">Lift Equipped</div>
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
<option value=""></option>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
</div>
{/* Vehicle Maintenance & Compliance would be added later */}
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
<DatePicker selected={oilChangeDate} onChange={(v) => setOilChangeDate(v)} />
</div>
<div className="me-4">
<div className="field-label">Last Emissions Inspection Date <span className="field-blurb float-right">1-year due cycle </span></div>
<DatePicker selected={emissionTestOn} onChange={(v) => setEmissionTestOn(v)} />
</div>
<div className="me-4">
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
<DatePicker selected={insuranceExpireOn} onChange={(v) => setInsuranceExpireOn(v)} />
</div>
<div className="me-4">
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
<DatePicker selected={titleRegistrationOn} onChange={(v) => setTitleRegistrationOn(v)} />
</div>
</div>
<h6 className="text-primary">Check List</h6>
<div className="app-main-content-fields-section column">
{checklist.map((item, index) => (<div className="mb-4" key={index}><input type="text" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
</div>))}
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
{/* Note would be added later */}
</div>
<h6 className="text-primary">Additional Information</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Note</div>
<textarea placeholder="Any Extra Details" value={note || ''} onChange={e => setNote(e.target.value)}/>
</div>
</div>
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
{error}
</div>}
@ -124,12 +191,53 @@ const CreateVehicle = () => {
</div>
</Tab>
<Tab eventKey="documents" title="Documents">
Coming soon...
{/* <Tab eventKey="documents" title="Documents">
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Vehicle Inspection Date<span className="required">*</span></div>
<DatePicker selected={yearlyInspectionDate} onChange={(v) => setYearlyInspectionDate(v)} />
</div>
<div className="me-4">
<div className="field-label">Yearly Vehicle Inspection Sheet<span className="required">*</span></div>
<label className="custom-file-upload">
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
<input
type="file"
onChange={(e) => setSelectedYearlyFile(e.target.files[0])}
/>
</label>
<div className="file-name">{ selectedYearlyFile && selectedYearlyFile?.name }</div>
</div>
</div>
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Vehicle Inspection Date <span className="required">*</span></div>
<DatePicker selected={monthlyInspectionDate} onChange={(v) => setMonthlyInspectionDate(v)} />
</div>
<div className="me-4">
<div className="field-label">Monthly Vehicle Inspection Sheet<span className="required">*</span></div>
<label className="custom-file-upload">
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
<input
type="file"
onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}
/>
</label>
<div className="file-name">{ selectedMothlyFile && selectedMothlyFile?.name }</div>
</div>
</div>
<div className="col-md-12 col-sm-12 col-xs-12">
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => saveDocuments()}> Save </button>
</div>
</Tab>
<Tab eventKey="Repair Records" title="Repair Records">
Coming soon...
</Tab>
</Tab> */}
</Tabs>
</div>
</div>

View File

@ -2,9 +2,12 @@ import React, {useEffect, useState} from "react";
import { useSelector,useDispatch } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { vehicleSlice, selectVehicleError } from "./../../store";
import { AuthService } from "../../services";
import { Archive } from "react-bootstrap-icons";
import { AuthService, VehicleService } from "../../services";
import { Archive, Upload } from "react-bootstrap-icons";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import DatePicker from "react-datepicker";
import moment from 'moment';
const UpdateVehicle = () => {
const navigate = useNavigate();
@ -23,6 +26,18 @@ const UpdateVehicle = () => {
const [mileage, setMileage] = useState();
const [capacity, setCapacity] = useState();
const [checklist, setChecklist] = useState(['']);
const [hasLiftEquip, setHasLiftEquip] = useState(undefined);
const [insuranceExpireOn, setInsuranceExpireOn] = useState(undefined);
const [titleRegistrationOn, setTitleRegistrationOn] = useState(undefined);
const [emissionTestOn, setEmissionTestOn] = useState(undefined);
const [oilChangeMileage, setOilChangeMileage] = useState(undefined);
const [oilChangeDate, setOilChangeDate] = useState(undefined);
const [vin, setVin] = useState('');
const [note, setNote] = useState('');
const [selectedMothlyFile, setSelectedMonthlyFile] = useState();
const [selectedYearlyFile, setSelectedYearlyFile] = useState();
const [monthlyInspectionDate, setMonthlyInspectionDate] = useState();
const [yearlyInspectionDate, setYearlyInspectionDate] = useState();
const error = useSelector(selectVehicleError);
useEffect(() => {
@ -48,6 +63,13 @@ const UpdateVehicle = () => {
setMileage(currentVehicle.mileage);
setCapacity(currentVehicle.capacity);
setChecklist(currentVehicle.checklist);
setHasLiftEquip(currentVehicle.has_lift_equip === true ? 'true': (currentVehicle?.has_lift_equip === false ? 'false' : undefined) );
setNote(currentVehicle?.note);
setVin(currentVehicle?.vin);
setInsuranceExpireOn(currentVehicle.insurance_expire_on && VehicleService.convertToDate(currentVehicle.insurance_expire_on));
setTitleRegistrationOn(currentVehicle?.title_registration_on && VehicleService.convertToDate(currentVehicle?.title_registration_on));
setEmissionTestOn(currentVehicle.title_registration_on && VehicleService.convertToDate(currentVehicle.title_registration_on));
setOilChangeDate(currentVehicle.oil_change_date && VehicleService.convertToDate(currentVehicle.oil_change_date));
}
}, [currentVehicle])
@ -83,7 +105,14 @@ const UpdateVehicle = () => {
make,
vehicle_model: vehicleModel,
status: 'active',
checklist
checklist,
note,
vin,
has_lift_equip: hasLiftEquip === 'true',
insurance_expire_on: moment(insuranceExpireOn).format('MM/DD/YYYY'),
title_registration_on: moment(titleRegistrationOn).format('MM/DD/YYYY'),
emission_test_on: moment(emissionTestOn).format('MM/DD/YYYY'),
oil_change_date: moment(oilChangeDate).format('MM/DD/YYYY')
};
dispatch(updateVehicle({id: params.id, data, redirectFun: redirectTo}));
}
@ -106,6 +135,20 @@ const UpdateVehicle = () => {
redirectTo();
}
const saveDocuments = () => {
if (selectedMothlyFile && monthlyInspectionDate) {
const monthlyFormData = new FormData();
monthlyFormData.append('file', selectedMothlyFile);
VehicleService.uploadVechileFile(monthlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'monthlyInspection', monthlyInspectionDate);
}
if (selectedYearlyFile && yearlyInspectionDate) {
const yearlyFormData = new FormData();
yearlyFormData.append('file', selectedYearlyFile);
VehicleService.uploadVechileFile(yearlyFormData, currentVehicle.id, currentVehicle.vehicle_number, 'yearlyInspection', yearlyInspectionDate);
}
redirectTo();
}
return (
<>
@ -142,19 +185,51 @@ const UpdateVehicle = () => {
</div>
<div className="app-main-content-fields-section">
<div className="me-4"><div className="field-label">Year</div><input type="text" value={year || ''} placeholder="e.g.,2016" onChange={e => setYear(e.target.value)}/></div>
<div className="me-4"><div className="field-label">Vin Number</div><input type="text" value={vin || ''} placeholder="e.g, 1FBAX2CM9KA34959" onChange={e => setVin(e.target.value)}/></div>
<div className="me-4"><div className="field-label">Licence Plate</div><input type="text" value={tag || ''} placeholder="e.g.,91579HT" onChange={e => setTag(e.target.value)}/></div>
<div className="me-4"><div className="field-label">GPS ID</div><input type="text" value={gps || ''} placeholder="e.g.,609671" onChange={e => setGps(e.target.value)}/></div>
<div className="me-4"><div className="field-label">EZPass</div><input type="text" value={ezpass || ''} placeholder="e.g.,NY12345" onChange={e => setEzpass(e.target.value)}/></div>
{/* Lift Equipped & VIN Number would be added later */}
<div className="me-4">
<div className="field-label">Lift Equipped</div>
<select value={hasLiftEquip} onChange={e => setHasLiftEquip(e.target.value)}>
<option value=""></option>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
</div>
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
<DatePicker selected={oilChangeDate} onChange={(v) => setOilChangeDate(v)} />
</div>
<div className="me-4">
<div className="field-label">Last Emissions Inspection Date <span className="field-blurb float-right">1-year due cycle </span></div>
<DatePicker selected={emissionTestOn} onChange={(v) => setEmissionTestOn(v)} />
</div>
<div className="me-4">
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
<DatePicker selected={insuranceExpireOn} onChange={(v) => setInsuranceExpireOn(v)} />
</div>
<div className="me-4">
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
<DatePicker selected={titleRegistrationOn} onChange={(v) => setTitleRegistrationOn(v)} />
</div>
</div>
{/* Vehicle Maintenance & Compliance would be added later */}
<h6 className="text-primary">Check List</h6>
<div className="app-main-content-fields-section column">
{checklist.map((item, index) => (<div className="mb-4" key={index}><input type="text" value={item} onChange={(e) => setChecklist([...checklist].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
<button className="btn btn-link btn-sm" onClick={(e) => setChecklist([...checklist].filter((value, index1) => index1 != index))}>Remove</button>
</div>))}
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
{/* Note would be added later */}
</div>
<h6 className="text-primary">Additional Information</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Note</div>
<textarea placeholder="Any Extra Details" value={note || ''} onChange={e => setNote(e.target.value)}/>
</div>
</div>
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
{error}
@ -166,7 +241,48 @@ const UpdateVehicle = () => {
</div>
</Tab>
<Tab eventKey="documents" title="Documents">
Coming soon...
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Vehicle Inspection Date<span className="required">*</span></div>
<DatePicker selected={yearlyInspectionDate} onChange={(v) => setYearlyInspectionDate(v)} />
</div>
<div className="me-4">
<div className="field-label">Yearly Vehicle Inspection Sheet<span className="required">*</span></div>
<label className="custom-file-upload">
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
<input
type="file"
onChange={(e) => setSelectedYearlyFile(e.target.files[0])}
/>
</label>
<div className="file-name">{ selectedYearlyFile && selectedYearlyFile?.name }</div>
</div>
</div>
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Vehicle Inspection Date <span className="required">*</span></div>
<DatePicker selected={monthlyInspectionDate} onChange={(v) => setMonthlyInspectionDate(v)} />
</div>
<div className="me-4">
<div className="field-label">Monthly Vehicle Inspection Sheet<span className="required">*</span></div>
<label className="custom-file-upload">
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload Files
<input
type="file"
onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}
/>
</label>
<div className="file-name">{ selectedMothlyFile && selectedMothlyFile?.name }</div>
</div>
</div>
<div className="col-md-12 col-sm-12 col-xs-12">
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={() => saveDocuments()}> Save </button>
</div>
</Tab>
<Tab eventKey="Repair Records" title="Repair Records">
Coming soon...

View File

@ -4,14 +4,23 @@ import { useSelector,useDispatch } from "react-redux";
import { AuthService, VehicleService } from "../../services";
import { vehicleSlice, selectVehicleError } from "./../../store";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Download, Pencil, Archive } from "react-bootstrap-icons";
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
import moment from "moment";
const ViewVehicle = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const urlParams = useParams();
const [currentVehicle, setCurrentVehicle] = useState(undefined);
const [monthlyDocs, setMonthlyDocs] = useState([]);
const [yearlyDocs, setYearlyDocs] = useState([]);
const [keyword, setKeyword] = useState('');
const [sortingMonthly, setSortingMonthly] = useState({key: '', order: ''});
const [sortingYearly, setSortingYearly] = useState({key: '', order: ''});
const [selectedItemsMonthly, setSelectedItemsMonthly] = useState([]);
const [selectedItemsYearly, setSelectedItemsYearly] = useState([]);
const [filteredMonthlyDocs, setFilteredMonthlyDocs] = useState(monthlyDocs);
const [filteredYearlyDocs, setFilteredYearlyDocs] = useState(yearlyDocs);
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
const redirectTo = () => {
@ -22,6 +31,18 @@ const ViewVehicle = () => {
navigate(`/vehicles/edit/${id}?redirect=list`)
}
const download = () => {
const downloadArr = [...selectedItemsMonthly, ...selectedItemsYearly];
downloadArr.forEach((url) => {
const a = document.createElement('a');
a.href= url;
a.download = '';
document.body.append(a);
a.click();
document.body.removeChild(a);
})
}
const deactivateVehicle = () => {
const data = {
status: 'inactive'
@ -31,6 +52,24 @@ const ViewVehicle = () => {
}
useEffect(() => {
const getInspectionDate = (name) => {
const arr1 = name.split('.');
const prefix = arr1[0];
if (prefix) {
const arr2 = prefix.split('_');
const dateNumber = arr2[arr2.length - 1];
return dateNumber ? new Date(parseInt(dateNumber)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : moment().format('MM/DD/YYYY');
} else {
return moment().format('MM/DD/YYYY');
}
}
const getAllDocuments = async (vid, name) => {
const monthlyInspectionDocs = (await VehicleService.getAllVechileFiles(vid, name, 'monthlyInspection'))?.data?.data?.files || [];
const yearlyInspectionDocs = (await VehicleService.getAllVechileFiles(vid, name, 'yearlyInspection'))?.data?.data?.files || [];
setMonthlyDocs(monthlyInspectionDocs?.map(item => ({ ...item, inspectionDate: getInspectionDate(item?.name) })));
setYearlyDocs(yearlyInspectionDocs?.map(item => ({ ...item, inspectionDate: getInspectionDate(item?.name) })));
};
if (!AuthService.canViewVechiles()) {
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
AuthService.logout();
@ -39,9 +78,207 @@ const ViewVehicle = () => {
if (!currentVehicle) {
VehicleService.getVehicle(urlParams.id).then((data) => {
setCurrentVehicle(data.data);
getAllDocuments(data.data?.id, data.data?.vehicle_number);
})
} else {
getAllDocuments(currentVehicle?.id, currentVehicle?.vehicle_number);
}
}, []);
useEffect(() => {
setFilteredMonthlyDocs(monthlyDocs.filter(item => item?.name?.toLowerCase().includes(keyword.toLowerCase()) || item?.inspectionDate?.includes(keyword.toLowerCase())));
setFilteredYearlyDocs(yearlyDocs.filter(item => item?.name?.toLowerCase().includes(keyword.toLowerCase()) || item?.inspectionDate?.includes(keyword.toLowerCase())));
}, [keyword, yearlyDocs, monthlyDocs]);
useEffect(() => {
const newYearlyDocs = [...yearlyDocs];
const sortedYearlyDocs = sortingYearly.key === '' ? newYearlyDocs : newYearlyDocs.sort((a, b) => {
return a[sortingYearly.key]?.localeCompare(b[sortingYearly.key]);
});
setYearlyDocs(
sortingMonthly.order === 'asc' ? sortedYearlyDocs : sortedYearlyDocs.reverse()
)
}, [sortingYearly]);
useEffect(() => {
const newMonthlyDocs = [...monthlyDocs];
const sortedMonthlyDocs = sortingMonthly.key === '' ? newMonthlyDocs : newMonthlyDocs.sort((a, b) => {
return a[sortingMonthly.key]?.localeCompare(b[sortingMonthly.key]);
});
setMonthlyDocs(
sortingMonthly.order === 'asc' ? sortedMonthlyDocs : sortedMonthlyDocs.reverse()
)
}, [sortingMonthly]);
const columnsMonthly = [
{
key: 'name',
label: 'Monthly Vehicle Inspection Sheet'
},
{
key: 'inspectionDate',
label: 'Vehicle Inspection Date'
},
{
key: 'createdAt',
label: 'Date Added'
}
];
const columnsYearly = [
{
key: 'name',
label: 'Yearly Vehicle Inspection Sheet'
},
{
key: 'inspectionDate',
label: 'Vehicle Inspection Date'
},
{
key: 'createdAt',
label: 'Date Added'
}
];
const sortTableWithFieldMonthly = (key) => {
let newSorting = {
key,
order: 'asc',
}
if (sortingMonthly.key === key && sortingMonthly.order === 'asc') {
newSorting = {...newSorting, order: 'desc'};
}
setSortingMonthly(newSorting);
}
const sortTableWithFieldYearly = (key) => {
let newSorting = {
key,
order: 'asc',
}
if (sortingYearly.key === key && sortingYearly.order === 'asc') {
newSorting = {...newSorting, order: 'desc'};
}
setSortingYearly(newSorting);
}
const toggleSelectedAllItemsMonthly = () => {
if (selectedItemsMonthly.length !== filteredMonthlyDocs.length || selectedItemsMonthly.length === 0) {
const newSelectedItems = [...filteredMonthlyDocs].map((doc) => doc.url);
setSelectedItemsMonthly(newSelectedItems);
} else {
setSelectedItemsMonthly([]);
}
}
const toggleSelectedAllItemsYearly = () => {
if (selectedItemsYearly.length !== filteredYearlyDocs.length || selectedItemsYearly.length === 0) {
const newSelectedItems = [...filteredYearlyDocs].map((doc) => doc.url);
setSelectedItemsYearly(newSelectedItems);
} else {
setSelectedItemsYearly([]);
}
}
const toggleItemYearly = (id) => {
if (selectedItemsYearly.includes(id)) {
const newSelectedItems = [...selectedItemsYearly].filter((item) => item !== id);
setSelectedItemsYearly(newSelectedItems);
} else {
const newSelectedItems = [...selectedItemsYearly, id];
setSelectedItemsYearly(newSelectedItems);
}
}
const toggleItemMonthly = (id) => {
if (selectedItemsMonthly.includes(id)) {
const newSelectedItems = [...selectedItemsMonthly].filter((item) => item !== id);
setSelectedItemsMonthly(newSelectedItems);
} else {
const newSelectedItems = [...selectedItemsMonthly, id];
setSelectedItemsMonthly(newSelectedItems);
}
}
const getSortingImgMonthly = (key) => {
return sortingMonthly.key === key ? (sortingMonthly.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
const getSortingImgYearly = (key) => {
return sortingYearly.key === key ? (sortingYearly.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
const checkSelectAllMonthly = () => {
return selectedItemsMonthly.length === filteredMonthlyDocs.length && selectedItemsMonthly.length > 0;
}
const checkSelectAllYearly = () => {
return selectedItemsYearly.length === filteredYearlyDocs.length && selectedItemsYearly.length > 0;
}
const tableMonthly = <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={checkSelectAllMonthly()} onClick={() => toggleSelectedAllItemsMonthly()}></input></th>
<th className="th-index">No.</th>
{
columnsMonthly.map((column, index) => <th className="sortable-header" key={index}>
{column.label} <span className="float-right" onClick={() => sortTableWithFieldMonthly(column.key)}><img src={`/images/${getSortingImgMonthly(column.key)}.png`}></img></span>
</th>)
}
</tr>
</thead>
<tbody>
{
filteredMonthlyDocs.map((doc, index) => <tr key={doc.url}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsMonthly.includes(doc?.url)} onClick={()=>toggleItemMonthly(doc?.url)}/></td>
<td className="td-index">{index + 1}</td>
<td> <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a> </td>
<td>{doc?.inspectionDate}</td>
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
</tr>)
}
</tbody>
</table>
</div>
</div>;
const tableYearly = <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={checkSelectAllYearly()} onClick={() => toggleSelectedAllItemsYearly()}></input></th>
<th className="th-index">No.</th>
{
columnsYearly.map((column, index) => <th className="sortable-header" key={index}>
{column.label} <span className="float-right" onClick={() => sortTableWithFieldYearly(column.key)}><img src={`/images/${getSortingImgYearly(column.key)}.png`}></img></span>
</th>)
}
</tr>
</thead>
<tbody>
{
filteredYearlyDocs.map((doc, index) => <tr key={doc.url}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsYearly.includes(doc?.url)} onClick={()=>toggleItemYearly(doc?.url)}/></td>
<td className="td-index">{index + 1}</td>
<td> <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a> </td>
<td>{doc?.inspectionDate}</td>
<td>{new Date(doc?.createdAt).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'})}</td>
</tr>)
}
</tbody>
</table>
</div>
</div>;
return (
<>
@ -91,6 +328,10 @@ const ViewVehicle = () => {
<div className="field-label">Year</div>
<div className="field-value">{currentVehicle?.year}</div>
</div>
<div className="field-body">
<div className="field-label">Vin Number</div>
<div className="field-value">{currentVehicle?.vin}</div>
</div>
<div className="field-body">
<div className="field-label">Licence Plate</div>
<div className="field-value">{currentVehicle?.tag}</div>
@ -103,25 +344,59 @@ const ViewVehicle = () => {
<div className="field-label">EZPass</div>
<div className="field-value">{currentVehicle?.ezpass}</div>
</div>
<div className="field-body">
<div className="field-label">Lift Equipped</div>
<div className="field-value">{currentVehicle?.has_lift_equip === true ? 'Yes' : (currentVehicle?.has_lift_equip === false? 'No' : '')}</div>
</div>
</div>
<h6 className="text-primary">Vehicle Maintenance & Compliance</h6>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Last Oil Change Date <span className="field-blurb float-right">1-month due cycle </span></div>
<div className="field-value">{currentVehicle?.oil_change_date}</div>
</div>
<div className="field-body">
<div className="field-label">Last Emissions Inspection Date <span className="field-blurb float-right">1-year due cycle </span></div>
<div className="field-value">{currentVehicle?.emission_test_on}</div>
</div>
<div className="field-body">
<div className="field-label">Insurance Expiration Date <span className="field-blurb float-right">1-year due cycle </span></div>
<div className="field-value">{currentVehicle?.insurance_expire_on}</div>
</div>
<div className="field-body">
<div className="field-label">Title Registration Date <span className="field-blurb float-right">1-year due cycle </span></div>
<div className="field-value">{currentVehicle?.title_registration_on}</div>
</div>
</div>
<h6 className="text-primary">Check List</h6>
<div className="app-main-content-fields-section column">
<ul>
{currentVehicle?.checklist?.map((item) => <li>{item}</li>)}
</ul>
</div>
<h6 className="text-primary">Additional Information</h6>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Note</div>
<div className="field-value">{currentVehicle?.note}</div>
</div>
</div>
</Tab>
<Tab eventKey="documents" title="Documents">
Coming soon...
<h6 className="text-primary">Yearly Vehicle Inspection</h6>
{tableYearly}
<h6 className="text-primary">Monthly Vehicle Inspection</h6>
{tableMonthly}
</Tab>
<Tab eventKey="Repair Records" title="Repair Records">
Coming soon...
</Tab>
</Tabs>
<div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={() => goToEdit(currentVehicle?.id)}><Pencil size={16} className="me-2"></Pencil>Edit</button>
<button className="btn btn-primary me-2" onClick={() => deactivateVehicle()}><Archive size={16} className="me-2"></Archive>Archive</button>
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>
</div>
</div>
</div>

View File

@ -30,6 +30,14 @@ const deleteEmployee = (id, data) => {
return http.put(`/employees/${id}`, data);
}
const uploadEmployeeFile = (data, employeeId, name, fileType) => {
return http.post(`/files/upload-physical?objectId=${employeeId}&name=${name}&fileType=${fileType}&model=employee`, data)
}
const getAllEmployeeFiles = (employeeId, name, fileType) => {
return http.get(`/files/uploadedDocs/employee/${employeeId}/type/${fileType}/name/${name}`)
}
const validatePassword = (password = '') => {
const lowercaseRegex = /[a-z]/;
const uppercaseRegex = /[A-Z]/;
@ -59,5 +67,7 @@ export const EmployeeService = {
updateEmployee,
deleteEmployee,
getEmployee,
validatePassword
validatePassword,
uploadEmployeeFile,
getAllEmployeeFiles
};

View File

@ -18,11 +18,27 @@ const getVehicle = (id) => {
return http.get(`/vehicles/${id}`);
};
const convertToDate = (dateString) => {
const [month, day, year] = dateString.split('/');
return new Date(Number(year), Number(month)-1, Number(day))
}
const uploadVechileFile = (data, vehicleId, name, fileType, date) => {
return http.post(`/files/upload-physical?objectId=${vehicleId}&name=${name}&fileType=${fileType}&model=vehicle&date=${date.getTime()}`, data)
}
const getAllVechileFiles = (vehicleId, name, fileType) => {
return http.get(`/files/uploadedDocs/vehicle/${vehicleId}/type/${fileType}/name/${name}`)
}
export const VehicleService = {
getAll,
getAllActiveVehicles,
updateVehicles,
createNewVehicles,
deleteVehicles,
getVehicle
getVehicle,
convertToDate,
uploadVechileFile,
getAllVechileFiles
};

View File

@ -8,6 +8,8 @@ var corsOptions = {
// const path = __dirname + '/client/build/';
const path = __dirname + '/app/views/'
const employeeUploadBasePath = '/www/wwwroot/upload/';
app.use('/files', express.static(employeeUploadBasePath));
app.use(cors(corsOptions));
// parse requests of content-type - application/json
app.use(bodyParser.json());