├── backend.rar ├── backend ├── .gitignore ├── README.md ├── app │ ├── config │ │ └── db.config.js │ ├── controllers │ │ ├── category.controller.js │ │ ├── measure.controller.js │ │ ├── tutorial.controller.js │ │ └── user.controller.js │ ├── models │ │ ├── category.model.js │ │ ├── index.js │ │ ├── measure.model.js │ │ ├── tutorial.model.js │ │ └── user.model.js │ ├── routes │ │ ├── category.routes.js │ │ ├── measure.routes.js │ │ ├── turorial.routes.js │ │ └── user.routes.js │ └── uploads │ │ ├── 123@gmail.com.pdf │ │ ├── 123@gmail.com111.pdf │ │ ├── 123@gmail.com112.pdf │ │ ├── 123@gmail.com113.pdf │ │ ├── 123@gmail.com123.pdf │ │ ├── 123@gmail.com222.pdf │ │ ├── 123@gmail.com333.pdf │ │ ├── 123@gmail.com345.pdf │ │ ├── 123@gmail.com444.pdf │ │ ├── 123@gmail.com555.pdf │ │ └── 123@gmail.com666.pdf ├── package.json └── server.js ├── frontend.rar └── frontend ├── .gitignore ├── build ├── annotate.svg ├── asset-manifest.json ├── check_icon.png ├── download.png ├── e7e915313973ba1d49f9bb2a1998626e.js ├── e7e915313973ba1d49f9bb2a1998626e.js.LICENSE.txt ├── favicon.ico ├── icon-cursor.svg ├── icon-deduct.svg ├── icon-poly.svg ├── icon-rect.svg ├── icon-undo.svg ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── plus_icon.png ├── robots.txt ├── ruler.png └── static │ ├── css │ ├── main.2f0e0917.css │ └── main.2f0e0917.css.map │ └── js │ ├── 611.f2ab1f06.chunk.js │ ├── 661.1d346a32.chunk.js │ ├── 661.1d346a32.chunk.js.LICENSE.txt │ ├── 661.1d346a32.chunk.js.map │ ├── 787.3b6992b7.chunk.js │ ├── 787.3b6992b7.chunk.js.map │ ├── main.d19fe8f1.js │ ├── main.d19fe8f1.js.LICENSE.txt │ └── main.d19fe8f1.js.map ├── package.json ├── public ├── annotate.svg ├── check_icon.png ├── document.pdf ├── download.png ├── example.pdf ├── favicon.ico ├── icon-cursor.svg ├── icon-deduct.svg ├── icon-poly.svg ├── icon-rect.svg ├── icon-search.svg ├── icon-undo.svg ├── index.html ├── logo192.png ├── logo512.png ├── main plans for richard.pdf ├── manifest.json ├── plus_icon.png ├── robots.txt ├── ruler.png └── spinner.gif └── src ├── App.js ├── App.test.js ├── actions ├── admin.js ├── alert.js ├── auth.js ├── result.js └── types.js ├── containers ├── Admin │ ├── Admin.css │ └── Admin.jsx ├── Construction │ ├── DraggableToolbar │ │ └── DraggableToolbar.js │ ├── DraggableWidget │ │ └── DraggableWidget.js │ ├── Main │ │ ├── Main.css │ │ ├── Main.js │ │ └── Main2.js │ └── Scale │ │ └── Scale.js ├── index.jsx └── signin │ ├── Signin.jsx │ └── Signin.scss ├── index.css ├── index.js ├── reducers ├── admin.js ├── alert.js ├── auth.js ├── index.js └── result.js ├── reportWebVitals.js ├── routes └── MyRoutes.jsx ├── setupProxy.js ├── setupTests.js ├── store.js └── utils ├── Spinner.js ├── api.js └── setAuthToken.js /backend.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend.rar -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Node.js Rest APIs with Express, Sequelize & MySQL example 2 | 3 | For more detail, please visit: 4 | > [Build Node.js Rest APIs with Express, Sequelize & MySQL](https://www.bezkoder.com/node-js-express-sequelize-mysql/) 5 | 6 | > [Build Node.js Rest APIs with Express & MySQL (without Sequelize)](https://www.bezkoder.com/node-js-rest-api-express-mysql/) 7 | 8 | > [Node.js Express File Upload Rest API example](https://www.bezkoder.com/node-js-express-file-upload/) 9 | 10 | > [Server side Pagination in Node.js with Sequelize and MySQL](https://www.bezkoder.com/node-js-sequelize-pagination-mysql/) 11 | 12 | > [Deploying/Hosting Node.js app on Heroku with MySQL database](https://www.bezkoder.com/deploy-node-js-app-heroku-cleardb-mysql/) 13 | 14 | Front-end that works well with this Back-end 15 | > [Axios Client](https://www.bezkoder.com/axios-request/) 16 | 17 | > [Angular 8](https://www.bezkoder.com/angular-crud-app/) / [Angular 10](https://www.bezkoder.com/angular-10-crud-app/) / [Angular 11](https://www.bezkoder.com/angular-11-crud-app/) / [Angular 12](https://www.bezkoder.com/angular-12-crud-app/) / [Angular 13](https://www.bezkoder.com/angular-13-crud-example/) / [Angular 14](https://www.bezkoder.com/angular-14-crud-example/) / [Angular 15](https://www.bezkoder.com/angular-15-crud-example/) / [Angular 16 Client](https://www.bezkoder.com/angular-16-crud-example/) 18 | 19 | > [Vue 2 Client](https://www.bezkoder.com/vue-js-crud-app/) / [Vue 3 Client](https://www.bezkoder.com/vue-3-crud/) / [Vuetify Client](https://www.bezkoder.com/vuetify-data-table-example/) 20 | 21 | > [React Client](https://www.bezkoder.com/react-crud-web-api/) / [React Redux Client](https://www.bezkoder.com/react-redux-crud-example/) 22 | 23 | ## More Practice 24 | 25 | Security: 26 | > [Node.js Express: JWT example | Token Based Authentication & Authorization](https://www.bezkoder.com/node-js-jwt-authentication-mysql/) 27 | 28 | Associations: 29 | > [Sequelize Associations: One-to-Many Relationship example](https://www.bezkoder.com/sequelize-associate-one-to-many/) 30 | 31 | > [Sequelize Associations: Many-to-Many Relationship example](https://www.bezkoder.com/sequelize-associate-many-to-many/) 32 | 33 | Fullstack: 34 | > [Vue.js + Node.js + Express + MySQL example](https://www.bezkoder.com/vue-js-node-js-express-mysql-crud-example/) 35 | 36 | > [Vue.js + Node.js + Express + MongoDB example](https://www.bezkoder.com/vue-node-express-mongodb-mevn-crud/) 37 | 38 | > [Angular 8 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-node-express-mysql/) 39 | 40 | > [Angular 10 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-10-node-js-express-mysql/) 41 | 42 | > [Angular 11 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-11-node-js-express-mysql/) 43 | 44 | > [Angular 12 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-12-node-js-express-mysql/) 45 | 46 | > [Angular 13 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-13-node-js-express-mysql/) 47 | 48 | > [Angular 14 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-14-node-js-express-mysql/) 49 | 50 | > [Angular 15 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-15-node-js-express-mysql/) 51 | 52 | > [Angular 16 + Node.js + Express + MySQL example](https://www.bezkoder.com/angular-16-node-js-express-mysql/) 53 | 54 | > [React + Node.js + Express + MySQL example](https://www.bezkoder.com/react-node-express-mysql/) 55 | 56 | Integration (run back-end & front-end on same server/port) 57 | > [Integrate React with Node.js Restful Services](https://www.bezkoder.com/integrate-react-express-same-server-port/) 58 | 59 | > [Integrate Angular with Node.js Restful Services](https://www.bezkoder.com/integrate-angular-10-node-js/) 60 | 61 | > [Integrate Vue with Node.js Restful Services](https://www.bezkoder.com/serve-vue-app-express/) 62 | 63 | ## Project setup 64 | ``` 65 | npm install 66 | ``` 67 | 68 | ### Run 69 | ``` 70 | node server.js 71 | ``` 72 | -------------------------------------------------------------------------------- /backend/app/config/db.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | HOST: "localhost", 3 | USER: "root", 4 | PASSWORD: "", 5 | DB: "plan", 6 | dialect: "mysql", 7 | pool: { 8 | max: 5, 9 | min: 0, 10 | acquire: 30000, 11 | idle: 10000 12 | } 13 | }; -------------------------------------------------------------------------------- /backend/app/controllers/category.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | 3 | const Category = db.categories; 4 | 5 | // Find a single user with an email 6 | exports.createCategory = (req, res) => { 7 | const { name, unit, subCategory } = req.body; 8 | Category.create({ 9 | name: name, 10 | unit: unit, 11 | subcategory: subCategory 12 | }) 13 | .then(data => { 14 | Category.findAll() 15 | .then(categories => res.send(categories)) 16 | .catch(err => res.status(201).send({message: err})) 17 | }) 18 | .catch(err => { 19 | res.status(500).send({ 20 | message: err.message || "Some error occurred while creating the category." 21 | }); 22 | }); 23 | }; 24 | 25 | exports.findAll = (req, res) => { 26 | Category.findAll({raw: true}) 27 | .then(categories => { 28 | res.status(200).send(categories); 29 | }) 30 | .catch(err => { 31 | res.status(500).send({ 32 | message: "Error occured while retrieving categories.", 33 | error: err.message 34 | }) 35 | }) 36 | } 37 | 38 | exports.deleteCategory = (req, res) => { 39 | console.log("OK") 40 | const {id} = req.params; 41 | Category.destroy({ 42 | where: {id} 43 | }) 44 | .then(numDeleted => { 45 | if (numDeleted === 1) { 46 | Category.findAll() 47 | .then(categories => res.send(categories)) 48 | .catch(err => res.status(201).send({message: err})) 49 | } else { 50 | res.status(404).send({ 51 | message: `Category with id ${categoryId} not found.` 52 | }); 53 | } 54 | }) 55 | .catch(err => { 56 | res.status(500).send({ 57 | message: "Error occurred while deleting the category.", 58 | error: err.message 59 | }); 60 | }); 61 | } 62 | 63 | exports.updateCategory = (req, res) => { 64 | const id = req.params.id; 65 | const maindata = req.body; 66 | 67 | Category.update( 68 | { 69 | subcategory: maindata, 70 | }, 71 | { 72 | where: {id}, 73 | }) 74 | .then(data => { 75 | Category.findAll() 76 | .then(categories => res.send(categories)) 77 | .catch(err => res.status(201).send({message: err})) 78 | }) 79 | .catch(err => console.log(err)) 80 | }; -------------------------------------------------------------------------------- /backend/app/controllers/measure.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const dbConfig = require("../config/db.config.js"); 3 | const fs = require("fs"); 4 | const Measure = db.measures; 5 | 6 | const Sequelize = require("sequelize"); 7 | const { Blob } = require("buffer"); 8 | const { dirname } = require("path"); 9 | const path = require("path"); 10 | const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, { 11 | host: dbConfig.HOST, 12 | dialect: dbConfig.dialect, 13 | operatorsAliases: false, 14 | 15 | pool: { 16 | max: dbConfig.pool.max, 17 | min: dbConfig.pool.min, 18 | acquire: dbConfig.pool.acquire, 19 | idle: dbConfig.pool.idle, 20 | }, 21 | }); 22 | // Find a single user with an email 23 | exports.save = async (req, res) => { 24 | let successFlag = true; 25 | const dataToSave = req.body; 26 | const project = await Measure.findAll({ 27 | where: { 28 | user: dataToSave[0].user, 29 | projectName: dataToSave[0].projectName, 30 | fileName: dataToSave[0].fileName, 31 | pageNumber: dataToSave[0].currentPageNumber, 32 | }, 33 | }); 34 | console.log("Project: ", project); 35 | if (project.length) { 36 | console.log("Project Found!"); 37 | Measure.destroy({ 38 | where: { 39 | user: dataToSave[0].user, 40 | projectName: dataToSave[0].projectName, 41 | fileName: dataToSave[0].fileName, 42 | pageNumber: dataToSave[0].currentPageNumber, 43 | }, 44 | // truncate: true, 45 | }).then(async (data) => { 46 | dataToSave.forEach((element) => { 47 | const { 48 | area, 49 | subarea, 50 | category, 51 | subcategory, 52 | type, 53 | unit, 54 | measure, 55 | result, 56 | price, 57 | location, 58 | deductRect, 59 | currentPageNumber, 60 | fileName, 61 | user, 62 | projectName, 63 | } = element; 64 | Measure.create({ 65 | area: area, 66 | subarea: subarea, 67 | category: category, 68 | subcategory: JSON.stringify(subcategory), 69 | type: type, 70 | unit: unit, 71 | measure: measure, 72 | HDP: 0, 73 | total: result, 74 | price: price, 75 | location: location, 76 | deductRect: deductRect, 77 | pageNumber: currentPageNumber, 78 | fileName: fileName, 79 | user: user, 80 | projectName: projectName, 81 | }).catch((err) => { 82 | successFlag = false; 83 | }); 84 | }); 85 | 86 | if (successFlag) { 87 | res.status(200).send({ 88 | respond: "success", 89 | }); 90 | } else { 91 | res.status(201).send({ 92 | msg: "Error occured while saving measured data to databse.", 93 | }); 94 | } 95 | }); 96 | } else { 97 | console.log("Project Not Found!"); 98 | dataToSave.forEach((element) => { 99 | const { 100 | area, 101 | subarea, 102 | category, 103 | subcategory, 104 | type, 105 | unit, 106 | measure, 107 | result, 108 | price, 109 | location, 110 | deductRect, 111 | currentPageNumber, 112 | fileName, 113 | user, 114 | projectName, 115 | } = element; 116 | Measure.create({ 117 | area: area, 118 | subarea: subarea, 119 | category: category, 120 | subcategory: JSON.stringify(subcategory), 121 | type: type, 122 | unit: unit, 123 | measure: measure, 124 | HDP: 0, 125 | total: result, 126 | price: price, 127 | location: JSON.stringify({location}), 128 | deductRect: deductRect, 129 | pageNumber: currentPageNumber, 130 | fileName: element.user + element.projectName + ".pdf", 131 | filePath: `app/uploads/${element.user + element.projectName}.pdf`, 132 | user: user, 133 | projectName: projectName, 134 | }).then(data => { 135 | console.log('created!'); 136 | }).catch((err) => { 137 | console.log('err========>: ', err, typeof location); 138 | successFlag = false; 139 | }); 140 | }); 141 | } 142 | }; 143 | //PDF file upload 144 | exports.createProject = async (req, res) => { 145 | console.log("req.body=========>: ", req.file.path); 146 | await sequelize.sync(); 147 | let filePath = `${req.file.path}`; 148 | fs.renameSync( 149 | filePath, 150 | `app/uploads/${req.body.user + req.body.ProjectName}.pdf` 151 | ); 152 | let fileName = `${req.body.user + req.body.ProjectName}.pdf`; 153 | // fs.renameSync(fileName, `${req.body.user + req.body.ProjectName}.pdf`); 154 | filePath = `app/uploads/${req.body.user + req.body.ProjectName}.pdf`; 155 | // Measure.update( 156 | // { filePath: filePath, fileName: fileName }, 157 | // { 158 | // where: { 159 | // user: req.body.user, 160 | // projectName: req.body.ProjectName, 161 | // }, 162 | // } 163 | // ); 164 | }; 165 | 166 | exports.getFileBuffer = async (req, res) => { 167 | let data = fs.readFileSync(`app/uploads/${req.body.fileName}`); 168 | console.log('buffer: ', data); 169 | return res.status(200).send({buffer: data}); 170 | }; 171 | 172 | exports.getProject = async (req, res) => { 173 | Measure.findAll({ 174 | where: { 175 | user: "123@gmail.com", 176 | }, 177 | attributes: ["projectName"], 178 | group: ["projectName"], 179 | raw: true, 180 | }) 181 | .then((data) => { 182 | console.log("ProjectFound=========>: ", data[0]); 183 | res.status(200).send({ data: data }); 184 | }) 185 | .catch((err) => { 186 | console.log("Project Not Found==========>: ", err); 187 | res.status(505).send({ 188 | msg: "Project Not Found!", 189 | }); 190 | }); 191 | }; 192 | 193 | exports.getProjectContent = async (req, res) => { 194 | Measure.findAll({ 195 | where: { 196 | user: req.body.user, 197 | projectName: req.body.projectName, 198 | }, 199 | }) 200 | .then((data) => { 201 | console.log("ProjectContentFound=========>: ", data); 202 | res.status(200).send({ data: data }); 203 | }) 204 | .catch((err) => { 205 | console.log("ProjectContent Not Found==========>: ", err); 206 | res.status(505).send({ 207 | msg: "Project Not Found!", 208 | }); 209 | }); 210 | }; 211 | -------------------------------------------------------------------------------- /backend/app/controllers/tutorial.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const Tutorial = db.tutorials; 3 | const Op = db.Sequelize.Op; 4 | 5 | // Create and Save a new Tutorial 6 | exports.create = (req, res) => { 7 | // Validate request 8 | if (!req.body.title) { 9 | res.status(400).send({ 10 | message: "Content can not be empty!" 11 | }); 12 | return; 13 | } 14 | 15 | // Create a Tutorial 16 | const tutorial = { 17 | title: req.body.title, 18 | description: req.body.description, 19 | published: req.body.published ? req.body.published : false 20 | }; 21 | 22 | // Save Tutorial in the database 23 | Tutorial.create(tutorial) 24 | .then(data => { 25 | res.send(data); 26 | }) 27 | .catch(err => { 28 | res.status(500).send({ 29 | message: 30 | err.message || "Some error occurred while creating the Tutorial." 31 | }); 32 | }); 33 | }; 34 | 35 | // Retrieve all Tutorials from the database. 36 | exports.findAll = (req, res) => { 37 | const title = req.query.title; 38 | var condition = title ? { title: { [Op.like]: `%${title}%` } } : null; 39 | 40 | Tutorial.findAll({ where: condition }) 41 | .then(data => { 42 | res.send(data); 43 | }) 44 | .catch(err => { 45 | res.status(500).send({ 46 | message: 47 | err.message || "Some error occurred while retrieving tutorials." 48 | }); 49 | }); 50 | }; 51 | 52 | // Find a single Tutorial with an id 53 | exports.findOne = (req, res) => { 54 | const id = req.params.id; 55 | 56 | Tutorial.findByPk(id) 57 | .then(data => { 58 | if (data) { 59 | res.send(data); 60 | } else { 61 | res.status(404).send({ 62 | message: `Cannot find Tutorial with id=${id}.` 63 | }); 64 | } 65 | }) 66 | .catch(err => { 67 | res.status(500).send({ 68 | message: "Error retrieving Tutorial with id=" + id 69 | }); 70 | }); 71 | }; 72 | 73 | // Update a Tutorial by the id in the request 74 | exports.update = (req, res) => { 75 | const id = req.params.id; 76 | 77 | Tutorial.update(req.body, { 78 | where: { id: id } 79 | }) 80 | .then(num => { 81 | if (num == 1) { 82 | res.send({ 83 | message: "Tutorial was updated successfully." 84 | }); 85 | } else { 86 | res.send({ 87 | message: `Cannot update Tutorial with id=${id}. Maybe Tutorial was not found or req.body is empty!` 88 | }); 89 | } 90 | }) 91 | .catch(err => { 92 | res.status(500).send({ 93 | message: "Error updating Tutorial with id=" + id 94 | }); 95 | }); 96 | }; 97 | 98 | // Delete a Tutorial with the specified id in the request 99 | exports.delete = (req, res) => { 100 | const id = req.params.id; 101 | 102 | Tutorial.destroy({ 103 | where: { id: id } 104 | }) 105 | .then(num => { 106 | if (num == 1) { 107 | res.send({ 108 | message: "Tutorial was deleted successfully!" 109 | }); 110 | } else { 111 | res.send({ 112 | message: `Cannot delete Tutorial with id=${id}. Maybe Tutorial was not found!` 113 | }); 114 | } 115 | }) 116 | .catch(err => { 117 | res.status(500).send({ 118 | message: "Could not delete Tutorial with id=" + id 119 | }); 120 | }); 121 | }; 122 | 123 | // Delete all Tutorials from the database. 124 | exports.deleteAll = (req, res) => { 125 | Tutorial.destroy({ 126 | where: {}, 127 | truncate: false 128 | }) 129 | .then(nums => { 130 | res.send({ message: `${nums} Tutorials were deleted successfully!` }); 131 | }) 132 | .catch(err => { 133 | res.status(500).send({ 134 | message: 135 | err.message || "Some error occurred while removing all tutorials." 136 | }); 137 | }); 138 | }; 139 | 140 | // find all published Tutorial 141 | exports.findAllPublished = (req, res) => { 142 | Tutorial.findAll({ where: { published: true } }) 143 | .then(data => { 144 | res.send(data); 145 | }) 146 | .catch(err => { 147 | res.status(500).send({ 148 | message: 149 | err.message || "Some error occurred while retrieving tutorials." 150 | }); 151 | }); 152 | }; 153 | -------------------------------------------------------------------------------- /backend/app/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | 3 | const User = db.users; 4 | 5 | // Find a single user with an email 6 | exports.findOne = (req, res) => { 7 | const {email, password} = req.body; 8 | 9 | User.findOne({ 10 | where:{email} 11 | }) 12 | .then(data => { 13 | if (data) { 14 | if(data.password !== password){ 15 | res.status(404).send({ 16 | message:"Password Not Match" 17 | }) 18 | } 19 | else{ 20 | res.send(data); 21 | } 22 | } 23 | else { 24 | res.status(404).send({ 25 | message: `User not exist` 26 | }); 27 | } 28 | }) 29 | .catch(err => { 30 | res.status(500).send({ 31 | message: "Error retrieving Tutorial with id=" + id 32 | }); 33 | }); 34 | }; -------------------------------------------------------------------------------- /backend/app/models/category.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize) => { 2 | const Category = sequelize.define("category", { 3 | name: { 4 | type: Sequelize.STRING 5 | }, 6 | unit: { 7 | type: Sequelize.STRING 8 | }, 9 | subcategory: { 10 | type: Sequelize.JSON 11 | } 12 | }); 13 | 14 | return Category; 15 | }; 16 | -------------------------------------------------------------------------------- /backend/app/models/index.js: -------------------------------------------------------------------------------- 1 | const dbConfig = require("../config/db.config.js"); 2 | 3 | const Sequelize = require("sequelize"); 4 | const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, { 5 | host: dbConfig.HOST, 6 | dialect: dbConfig.dialect, 7 | operatorsAliases: false, 8 | 9 | pool: { 10 | max: dbConfig.pool.max, 11 | min: dbConfig.pool.min, 12 | acquire: dbConfig.pool.acquire, 13 | idle: dbConfig.pool.idle 14 | } 15 | }); 16 | 17 | const db = {}; 18 | 19 | db.Sequelize = Sequelize; 20 | db.sequelize = sequelize; 21 | 22 | db.tutorials = require("./tutorial.model.js")(sequelize, Sequelize); 23 | db.users = require('./user.model.js')(sequelize, Sequelize); 24 | db.categories = require("./category.model.js")(sequelize, Sequelize); 25 | db.measures = require("./measure.model.js")(sequelize, Sequelize); 26 | 27 | 28 | module.exports = db; -------------------------------------------------------------------------------- /backend/app/models/measure.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize) => { 2 | const Measure = sequelize.define("measure", { 3 | area: { 4 | type: Sequelize.STRING 5 | }, 6 | subarea: { 7 | type: Sequelize.STRING 8 | }, 9 | category: { 10 | type: Sequelize.STRING 11 | }, 12 | subcategory: { 13 | type: Sequelize.JSON 14 | }, 15 | type: { 16 | type: Sequelize.STRING 17 | }, 18 | unit: { 19 | type: Sequelize.STRING 20 | }, 21 | measure: { 22 | type: Sequelize.DOUBLE 23 | }, 24 | total: { 25 | type: Sequelize.DOUBLE 26 | }, 27 | price: { 28 | type: Sequelize.JSON 29 | }, 30 | location: { 31 | type: Sequelize.JSON 32 | }, 33 | deductRect: { 34 | type: Sequelize.JSON 35 | }, 36 | pageNumber: { 37 | type: Sequelize.INTEGER 38 | }, 39 | fileName: { 40 | type: Sequelize.STRING 41 | }, 42 | filePath: { 43 | type: Sequelize.STRING 44 | }, 45 | user: { 46 | type: Sequelize.STRING 47 | }, 48 | projectName: { 49 | type: Sequelize.STRING 50 | } 51 | }); 52 | 53 | return Measure; 54 | }; 55 | -------------------------------------------------------------------------------- /backend/app/models/tutorial.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize) => { 2 | const Tutorial = sequelize.define("tutorial", { 3 | title: { 4 | type: Sequelize.STRING 5 | }, 6 | description: { 7 | type: Sequelize.STRING 8 | }, 9 | published: { 10 | type: Sequelize.BOOLEAN 11 | } 12 | }); 13 | 14 | return Tutorial; 15 | }; 16 | -------------------------------------------------------------------------------- /backend/app/models/user.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize) => { 2 | const User = sequelize.define("user", { 3 | email: { 4 | type: Sequelize.STRING 5 | }, 6 | password: { 7 | type: Sequelize.STRING 8 | }, 9 | role: { 10 | type: Sequelize.BOOLEAN 11 | } 12 | }); 13 | 14 | return User; 15 | }; 16 | -------------------------------------------------------------------------------- /backend/app/routes/category.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const categories = require("../controllers/category.controller.js"); 3 | 4 | var router = require("express").Router(); 5 | 6 | 7 | // Retrieve all Tutorials 8 | router.get("/", categories.findAll); 9 | 10 | // Create New Category 11 | router.put("/", categories.createCategory); 12 | 13 | // Update one item in Category 14 | router.post("/:id", categories.updateCategory); 15 | 16 | // Delete a Tutorial with id 17 | router.delete("/:id", categories.deleteCategory); 18 | 19 | 20 | app.use('/api/category', router); 21 | }; 22 | -------------------------------------------------------------------------------- /backend/app/routes/measure.routes.js: -------------------------------------------------------------------------------- 1 | const multer = require("multer"); 2 | 3 | const storage = multer.diskStorage({ 4 | destination: (req, file, cb) => { 5 | cb(null, "./app/uploads"); 6 | }, 7 | filename: (req, file, cb) => { 8 | cb(null, file.originalname); 9 | } 10 | }); 11 | 12 | module.exports = (app) => { 13 | const measure = require("../controllers/measure.controller.js"); 14 | 15 | var router = require("express").Router(); 16 | 17 | router.post("/create-project", multer({ storage}).single("File"), measure.createProject); 18 | router.post("/get-project", measure.getProject); 19 | router.post("/getproject-content", measure.getProjectContent); 20 | router.post("/getfile-buffer", measure.getFileBuffer); 21 | router.put("/", measure.save); 22 | 23 | app.use("/api/measure", router); 24 | }; -------------------------------------------------------------------------------- /backend/app/routes/turorial.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const tutorials = require("../controllers/tutorial.controller.js"); 3 | 4 | var router = require("express").Router(); 5 | 6 | // Create a new Tutorial 7 | router.post("/", tutorials.create); 8 | 9 | // Retrieve all Tutorials 10 | router.get("/", tutorials.findAll); 11 | 12 | // Retrieve all published Tutorials 13 | router.get("/published", tutorials.findAllPublished); 14 | 15 | // Retrieve a single Tutorial with id 16 | router.get("/:id", tutorials.findOne); 17 | 18 | // Update a Tutorial with id 19 | router.put("/:id", tutorials.update); 20 | 21 | // Delete a Tutorial with id 22 | router.delete("/:id", tutorials.delete); 23 | 24 | // Delete all Tutorials 25 | router.delete("/", tutorials.deleteAll); 26 | 27 | app.use('/api/tutorials', router); 28 | }; 29 | -------------------------------------------------------------------------------- /backend/app/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | const user = require("../controllers/user.controller.js"); 3 | 4 | var router = require("express").Router(); 5 | 6 | // Retrieve user by Email 7 | router.post("/", user.findOne); 8 | 9 | app.use("/api/users", router); 10 | }; 11 | -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com111.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com111.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com112.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com112.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com113.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com113.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com123.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com123.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com222.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com222.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com333.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com333.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com345.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com345.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com444.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com444.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com555.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com555.pdf -------------------------------------------------------------------------------- /backend/app/uploads/123@gmail.com666.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/backend/app/uploads/123@gmail.com666.pdf -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-express-sequelize-mysql", 3 | "version": "1.0.0", 4 | "description": "Node.js Rest Apis with Express, Sequelize & MySQL", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server", 8 | "server": "nodemon server", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "nodejs", 13 | "express", 14 | "rest", 15 | "api", 16 | "sequelize", 17 | "mysql" 18 | ], 19 | "author": "bezkoder", 20 | "license": "ISC", 21 | "dependencies": { 22 | "body-parser": "^1.20.2", 23 | "cors": "^2.8.5", 24 | "express": "^4.18.2", 25 | "JASON": "^0.1.3", 26 | "multer": "^1.4.5-lts.1", 27 | "mysql2": "^2.3.3", 28 | "nodemon": "^3.0.1", 29 | "sequelize": "^6.32.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const bodyParser = require("body-parser"); 4 | const path = require("path") 5 | 6 | const app = express(); 7 | 8 | app.use(cors()); 9 | 10 | // parse requests of content-type - application/json 11 | // app.use(express.json()); 12 | 13 | // // parse requests of content-type - application/x-www-form-urlencoded 14 | // app.use(express.urlencoded({ extended: true })); 15 | 16 | app.use(bodyParser.json()); 17 | app.use(bodyParser.urlencoded({ extended: true })); 18 | const db = require("./app/models"); 19 | 20 | app.use(express.static(path.resolve(__dirname, "./app/uploads"))); 21 | 22 | db.sequelize 23 | .sync() 24 | .then(() => { 25 | console.log("Synced db."); 26 | }) 27 | .catch((err) => { 28 | console.log("Failed to sync db: " + err.message); 29 | }); 30 | 31 | // // drop the table if it already exists 32 | // db.sequelize.sync({ force: true }).then(() => { 33 | // console.log("Drop and re-sync db."); 34 | // }); 35 | 36 | require("./app/routes/turorial.routes")(app); 37 | require("./app/routes/user.routes")(app); 38 | require("./app/routes/category.routes")(app); 39 | require("./app/routes/measure.routes")(app); 40 | 41 | // set port, listen for requests 42 | const PORT = process.env.PORT || 5000; 43 | app.listen(PORT, () => { 44 | console.log(`Server is running on port ${PORT}.`); 45 | }); 46 | -------------------------------------------------------------------------------- /frontend.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend.rar -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /frontend/build/annotate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.2f0e0917.css", 4 | "main.js": "/static/js/main.d19fe8f1.js", 5 | "static/js/611.f2ab1f06.chunk.js": "/static/js/611.f2ab1f06.chunk.js", 6 | "static/js/787.3b6992b7.chunk.js": "/static/js/787.3b6992b7.chunk.js", 7 | "static/js/661.1d346a32.chunk.js": "/static/js/661.1d346a32.chunk.js", 8 | "pdf.worker.js": "/e7e915313973ba1d49f9bb2a1998626e.js", 9 | "index.html": "/index.html", 10 | "main.2f0e0917.css.map": "/static/css/main.2f0e0917.css.map", 11 | "main.d19fe8f1.js.map": "/static/js/main.d19fe8f1.js.map", 12 | "787.3b6992b7.chunk.js.map": "/static/js/787.3b6992b7.chunk.js.map", 13 | "661.1d346a32.chunk.js.map": "/static/js/661.1d346a32.chunk.js.map" 14 | }, 15 | "entrypoints": [ 16 | "static/css/main.2f0e0917.css", 17 | "static/js/main.d19fe8f1.js" 18 | ] 19 | } -------------------------------------------------------------------------------- /frontend/build/check_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/check_icon.png -------------------------------------------------------------------------------- /frontend/build/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/download.png -------------------------------------------------------------------------------- /frontend/build/e7e915313973ba1d49f9bb2a1998626e.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @licstart The following is the entire license notice for the 3 | * Javascript code in this page 4 | * 5 | * Copyright 2021 Mozilla Foundation 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * @licend The above is the entire license notice for the 20 | * Javascript code in this page 21 | */ 22 | -------------------------------------------------------------------------------- /frontend/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/favicon.ico -------------------------------------------------------------------------------- /frontend/build/icon-cursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/build/icon-deduct.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/build/icon-poly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/build/icon-rect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/build/icon-undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/build/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /frontend/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/logo192.png -------------------------------------------------------------------------------- /frontend/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/logo512.png -------------------------------------------------------------------------------- /frontend/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/build/plus_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/plus_icon.png -------------------------------------------------------------------------------- /frontend/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/build/ruler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/build/ruler.png -------------------------------------------------------------------------------- /frontend/build/static/js/611.f2ab1f06.chunk.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunkchurch=self.webpackChunkchurch||[]).push([[611],{3414:function(){},172:function(){},2001:function(){},3779:function(){},6558:function(){},2258:function(){}}]); -------------------------------------------------------------------------------- /frontend/build/static/js/661.1d346a32.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @licstart The following is the entire license notice for the 3 | * Javascript code in this page 4 | * 5 | * Copyright 2021 Mozilla Foundation 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * @licend The above is the entire license notice for the 20 | * Javascript code in this page 21 | */ 22 | -------------------------------------------------------------------------------- /frontend/build/static/js/787.3b6992b7.chunk.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkchurch=self.webpackChunkchurch||[]).push([[787],{787:function(e,t,n){n.r(t),n.d(t,{getCLS:function(){return y},getFCP:function(){return h},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},h=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),f((function(){p.takeRecords().map(v),n(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTimeperformance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}}}]); 2 | //# sourceMappingURL=787.3b6992b7.chunk.js.map -------------------------------------------------------------------------------- /frontend/build/static/js/787.3b6992b7.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"static/js/787.3b6992b7.chunk.js","mappings":"iQAAA,IAAIA,EAAEC,EAAEC,EAAEC,EAAEC,EAAE,SAASJ,EAAEC,GAAG,MAAM,CAACI,KAAKL,EAAEM,WAAM,IAASL,GAAG,EAAEA,EAAEM,MAAM,EAAEC,QAAQ,GAAGC,GAAG,MAAMC,OAAOC,KAAKC,MAAM,KAAKF,OAAOG,KAAKC,MAAM,cAAcD,KAAKE,UAAU,QAAQC,EAAE,SAAShB,EAAEC,GAAG,IAAI,GAAGgB,oBAAoBC,oBAAoBC,SAASnB,GAAG,CAAC,GAAG,gBAAgBA,KAAK,2BAA2BoB,MAAM,OAAO,IAAIlB,EAAE,IAAIe,qBAAqB,SAASjB,GAAG,OAAOA,EAAEqB,aAAaC,IAAIrB,MAAM,OAAOC,EAAEqB,QAAQ,CAACC,KAAKxB,EAAEyB,UAAS,IAAKvB,GAAG,MAAMF,MAAM0B,EAAE,SAAS1B,EAAEC,GAAG,IAAIC,EAAE,SAASA,EAAEC,GAAG,aAAaA,EAAEqB,MAAM,WAAWG,SAASC,kBAAkB5B,EAAEG,GAAGF,IAAI4B,oBAAoB,mBAAmB3B,GAAE,GAAI2B,oBAAoB,WAAW3B,GAAE,MAAO4B,iBAAiB,mBAAmB5B,GAAE,GAAI4B,iBAAiB,WAAW5B,GAAE,IAAK6B,EAAE,SAAS/B,GAAG8B,iBAAiB,YAAY,SAAS7B,GAAGA,EAAE+B,WAAWhC,EAAEC,MAAK,IAAKgC,EAAE,SAASjC,EAAEC,EAAEC,GAAG,IAAIC,EAAE,OAAO,SAASC,GAAGH,EAAEK,OAAO,IAAIF,GAAGF,KAAKD,EAAEM,MAAMN,EAAEK,OAAOH,GAAG,IAAIF,EAAEM,YAAO,IAASJ,KAAKA,EAAEF,EAAEK,MAAMN,EAAEC,OAAOiC,GAAG,EAAEC,EAAE,WAAW,MAAM,WAAWR,SAASC,gBAAgB,EAAE,KAAKQ,EAAE,WAAWV,GAAG,SAAS1B,GAAG,IAAIC,EAAED,EAAEqC,UAAUH,EAAEjC,KAAI,IAAKqC,EAAE,WAAW,OAAOJ,EAAE,IAAIA,EAAEC,IAAIC,IAAIL,GAAG,WAAWQ,YAAY,WAAWL,EAAEC,IAAIC,MAAM,OAAO,CAAKI,sBAAkB,OAAON,KAAKO,EAAE,SAASzC,EAAEC,GAAG,IAAIC,EAAEC,EAAEmC,IAAIZ,EAAEtB,EAAE,OAAO8B,EAAE,SAASlC,GAAG,2BAA2BA,EAAEK,OAAO+B,GAAGA,EAAEM,aAAa1C,EAAE2C,UAAUxC,EAAEqC,kBAAkBd,EAAEpB,MAAMN,EAAE2C,UAAUjB,EAAElB,QAAQoC,KAAK5C,GAAGE,GAAE,MAAOiC,EAAEU,OAAOC,aAAaA,YAAYC,kBAAkBD,YAAYC,iBAAiB,0BAA0B,GAAGX,EAAED,EAAE,KAAKnB,EAAE,QAAQkB,IAAIC,GAAGC,KAAKlC,EAAE+B,EAAEjC,EAAE0B,EAAEzB,GAAGkC,GAAGD,EAAEC,GAAGJ,GAAG,SAAS5B,GAAGuB,EAAEtB,EAAE,OAAOF,EAAE+B,EAAEjC,EAAE0B,EAAEzB,GAAG+C,uBAAuB,WAAWA,uBAAuB,WAAWtB,EAAEpB,MAAMwC,YAAYlC,MAAMT,EAAEkC,UAAUnC,GAAE,cAAe+C,GAAE,EAAGC,GAAG,EAAEC,EAAE,SAASnD,EAAEC,GAAGgD,IAAIR,GAAG,SAASzC,GAAGkD,EAAElD,EAAEM,SAAS2C,GAAE,GAAI,IAAI/C,EAAEC,EAAE,SAASF,GAAGiD,GAAG,GAAGlD,EAAEC,IAAIiC,EAAE9B,EAAE,MAAM,GAAG+B,EAAE,EAAEC,EAAE,GAAGE,EAAE,SAAStC,GAAG,IAAIA,EAAEoD,eAAe,CAAC,IAAInD,EAAEmC,EAAE,GAAGjC,EAAEiC,EAAEA,EAAEiB,OAAO,GAAGlB,GAAGnC,EAAE2C,UAAUxC,EAAEwC,UAAU,KAAK3C,EAAE2C,UAAU1C,EAAE0C,UAAU,KAAKR,GAAGnC,EAAEM,MAAM8B,EAAEQ,KAAK5C,KAAKmC,EAAEnC,EAAEM,MAAM8B,EAAE,CAACpC,IAAImC,EAAED,EAAE5B,QAAQ4B,EAAE5B,MAAM6B,EAAED,EAAE1B,QAAQ4B,EAAElC,OAAOiD,EAAEnC,EAAE,eAAesB,GAAGa,IAAIjD,EAAE+B,EAAE9B,EAAE+B,EAAEjC,GAAGyB,GAAG,WAAWyB,EAAEG,cAAchC,IAAIgB,GAAGpC,GAAE,MAAO6B,GAAG,WAAWI,EAAE,EAAEe,GAAG,EAAEhB,EAAE9B,EAAE,MAAM,GAAGF,EAAE+B,EAAE9B,EAAE+B,EAAEjC,QAAQsD,EAAE,CAACC,SAAQ,EAAGC,SAAQ,GAAIC,EAAE,IAAI/C,KAAKgD,EAAE,SAASxD,EAAEC,GAAGJ,IAAIA,EAAEI,EAAEH,EAAEE,EAAED,EAAE,IAAIS,KAAKiD,EAAE/B,qBAAqBgC,MAAMA,EAAE,WAAW,GAAG5D,GAAG,GAAGA,EAAEC,EAAEwD,EAAE,CAAC,IAAItD,EAAE,CAAC0D,UAAU,cAAczD,KAAKL,EAAEwB,KAAKuC,OAAO/D,EAAE+D,OAAOC,WAAWhE,EAAEgE,WAAWrB,UAAU3C,EAAEqC,UAAU4B,gBAAgBjE,EAAEqC,UAAUpC,GAAGE,EAAE+D,SAAS,SAASlE,GAAGA,EAAEI,MAAMD,EAAE,KAAKgE,EAAE,SAASnE,GAAG,GAAGA,EAAEgE,WAAW,CAAC,IAAI/D,GAAGD,EAAEqC,UAAU,KAAK,IAAI1B,KAAKmC,YAAYlC,OAAOZ,EAAEqC,UAAU,eAAerC,EAAEwB,KAAK,SAASxB,EAAEC,GAAG,IAAIC,EAAE,WAAWyD,EAAE3D,EAAEC,GAAGG,KAAKD,EAAE,WAAWC,KAAKA,EAAE,WAAWyB,oBAAoB,YAAY3B,EAAEqD,GAAG1B,oBAAoB,gBAAgB1B,EAAEoD,IAAIzB,iBAAiB,YAAY5B,EAAEqD,GAAGzB,iBAAiB,gBAAgB3B,EAAEoD,GAA9N,CAAkOtD,EAAED,GAAG2D,EAAE1D,EAAED,KAAK4D,EAAE,SAAS5D,GAAG,CAAC,YAAY,UAAU,aAAa,eAAekE,SAAS,SAASjE,GAAG,OAAOD,EAAEC,EAAEkE,EAAEZ,OAAOa,EAAE,SAASlE,EAAEgC,GAAG,IAAIC,EAAEC,EAAEE,IAAIG,EAAErC,EAAE,OAAO6C,EAAE,SAASjD,GAAGA,EAAE2C,UAAUP,EAAEI,kBAAkBC,EAAEnC,MAAMN,EAAEiE,gBAAgBjE,EAAE2C,UAAUF,EAAEjC,QAAQoC,KAAK5C,GAAGmC,GAAE,KAAMe,EAAElC,EAAE,cAAciC,GAAGd,EAAEF,EAAE/B,EAAEuC,EAAEP,GAAGgB,GAAGxB,GAAG,WAAWwB,EAAEI,cAAchC,IAAI2B,GAAGC,EAAER,gBAAe,GAAIQ,GAAGnB,GAAG,WAAW,IAAIf,EAAEyB,EAAErC,EAAE,OAAO+B,EAAEF,EAAE/B,EAAEuC,EAAEP,GAAG/B,EAAE,GAAGF,GAAG,EAAED,EAAE,KAAK4D,EAAE9B,kBAAkBd,EAAEiC,EAAE9C,EAAEyC,KAAK5B,GAAG6C,QAAQQ,EAAE,GAAGC,EAAE,SAAStE,EAAEC,GAAG,IAAIC,EAAEC,EAAEmC,IAAIJ,EAAE9B,EAAE,OAAO+B,EAAE,SAASnC,GAAG,IAAIC,EAAED,EAAE2C,UAAU1C,EAAEE,EAAEqC,kBAAkBN,EAAE5B,MAAML,EAAEiC,EAAE1B,QAAQoC,KAAK5C,GAAGE,MAAMkC,EAAEpB,EAAE,2BAA2BmB,GAAG,GAAGC,EAAE,CAAClC,EAAE+B,EAAEjC,EAAEkC,EAAEjC,GAAG,IAAIwC,EAAE,WAAW4B,EAAEnC,EAAEzB,MAAM2B,EAAEkB,cAAchC,IAAIa,GAAGC,EAAEM,aAAa2B,EAAEnC,EAAEzB,KAAI,EAAGP,GAAE,KAAM,CAAC,UAAU,SAASgE,SAAS,SAASlE,GAAG8B,iBAAiB9B,EAAEyC,EAAE,CAAC8B,MAAK,EAAGd,SAAQ,OAAQ/B,EAAEe,GAAE,GAAIV,GAAG,SAAS5B,GAAG+B,EAAE9B,EAAE,OAAOF,EAAE+B,EAAEjC,EAAEkC,EAAEjC,GAAG+C,uBAAuB,WAAWA,uBAAuB,WAAWd,EAAE5B,MAAMwC,YAAYlC,MAAMT,EAAEkC,UAAUgC,EAAEnC,EAAEzB,KAAI,EAAGP,GAAE,cAAesE,EAAE,SAASxE,GAAG,IAAIC,EAAEC,EAAEE,EAAE,QAAQH,EAAE,WAAW,IAAI,IAAIA,EAAE6C,YAAY2B,iBAAiB,cAAc,IAAI,WAAW,IAAIzE,EAAE8C,YAAY4B,OAAOzE,EAAE,CAAC6D,UAAU,aAAanB,UAAU,GAAG,IAAI,IAAIzC,KAAKF,EAAE,oBAAoBE,GAAG,WAAWA,IAAID,EAAEC,GAAGW,KAAK8D,IAAI3E,EAAEE,GAAGF,EAAE4E,gBAAgB,IAAI,OAAO3E,EAAhL,GAAqL,GAAGC,EAAEI,MAAMJ,EAAEK,MAAMN,EAAE4E,cAAc3E,EAAEI,MAAM,GAAGJ,EAAEI,MAAMwC,YAAYlC,MAAM,OAAOV,EAAEM,QAAQ,CAACP,GAAGD,EAAEE,GAAG,MAAMF,MAAM,aAAa2B,SAASmD,WAAWvC,WAAWtC,EAAE,GAAG6B,iBAAiB,QAAQ,WAAW,OAAOS,WAAWtC,EAAE","sources":["../node_modules/web-vitals/dist/web-vitals.js"],"sourcesContent":["var e,t,n,i,r=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:\"v2-\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12)}},a=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if(\"first-input\"===e&&!(\"PerformanceEventTiming\"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},o=function(e,t){var n=function n(i){\"pagehide\"!==i.type&&\"hidden\"!==document.visibilityState||(e(i),t&&(removeEventListener(\"visibilitychange\",n,!0),removeEventListener(\"pagehide\",n,!0)))};addEventListener(\"visibilitychange\",n,!0),addEventListener(\"pagehide\",n,!0)},u=function(e){addEventListener(\"pageshow\",(function(t){t.persisted&&e(t)}),!0)},c=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},f=-1,s=function(){return\"hidden\"===document.visibilityState?0:1/0},m=function(){o((function(e){var t=e.timeStamp;f=t}),!0)},v=function(){return f<0&&(f=s(),m(),u((function(){setTimeout((function(){f=s(),m()}),0)}))),{get firstHiddenTime(){return f}}},d=function(e,t){var n,i=v(),o=r(\"FCP\"),f=function(e){\"first-contentful-paint\"===e.name&&(m&&m.disconnect(),e.startTime-1&&e(t)},f=r(\"CLS\",0),s=0,m=[],v=function(e){if(!e.hadRecentInput){var t=m[0],i=m[m.length-1];s&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(s+=e.value,m.push(e)):(s=e.value,m=[e]),s>f.value&&(f.value=s,f.entries=m,n())}},h=a(\"layout-shift\",v);h&&(n=c(i,f,t),o((function(){h.takeRecords().map(v),n(!0)})),u((function(){s=0,l=-1,f=r(\"CLS\",0),n=c(i,f,t)})))},T={passive:!0,capture:!0},y=new Date,g=function(i,r){e||(e=r,t=i,n=new Date,w(removeEventListener),E())},E=function(){if(t>=0&&t1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,t){var n=function(){g(e,t),r()},i=function(){r()},r=function(){removeEventListener(\"pointerup\",n,T),removeEventListener(\"pointercancel\",i,T)};addEventListener(\"pointerup\",n,T),addEventListener(\"pointercancel\",i,T)}(t,e):g(t,e)}},w=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(t){return e(t,S,T)}))},L=function(n,f){var s,m=v(),d=r(\"FID\"),p=function(e){e.startTimeperformance.now())return;n.entries=[t],e(n)}catch(e){}},\"complete\"===document.readyState?setTimeout(t,0):addEventListener(\"load\",(function(){return setTimeout(t,0)}))};export{h as getCLS,d as getFCP,L as getFID,F as getLCP,P as getTTFB};\n"],"names":["e","t","n","i","r","name","value","delta","entries","id","concat","Date","now","Math","floor","random","a","PerformanceObserver","supportedEntryTypes","includes","self","getEntries","map","observe","type","buffered","o","document","visibilityState","removeEventListener","addEventListener","u","persisted","c","f","s","m","timeStamp","v","setTimeout","firstHiddenTime","d","disconnect","startTime","push","window","performance","getEntriesByName","requestAnimationFrame","p","l","h","hadRecentInput","length","takeRecords","T","passive","capture","y","g","w","E","entryType","target","cancelable","processingStart","forEach","S","L","b","F","once","P","getEntriesByType","timing","max","navigationStart","responseStart","readyState"],"sourceRoot":""} -------------------------------------------------------------------------------- /frontend/build/static/js/main.d19fe8f1.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 14 | 15 | /*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */ 16 | 17 | /** 18 | * @license 19 | * Lodash 20 | * Copyright OpenJS Foundation and other contributors 21 | * Released under MIT license 22 | * Based on Underscore.js 1.8.3 23 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 24 | */ 25 | 26 | /** 27 | * @license React 28 | * react-is.production.min.js 29 | * 30 | * Copyright (c) Facebook, Inc. and its affiliates. 31 | * 32 | * This source code is licensed under the MIT license found in the 33 | * LICENSE file in the root directory of this source tree. 34 | */ 35 | 36 | /** 37 | * @license React 38 | * use-sync-external-store-shim.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** 47 | * @license React 48 | * use-sync-external-store-shim/with-selector.production.min.js 49 | * 50 | * Copyright (c) Facebook, Inc. and its affiliates. 51 | * 52 | * This source code is licensed under the MIT license found in the 53 | * LICENSE file in the root directory of this source tree. 54 | */ 55 | 56 | /** 57 | * @licstart The following is the entire license notice for the 58 | * Javascript code in this page 59 | * 60 | * Copyright 2021 Mozilla Foundation 61 | * 62 | * Licensed under the Apache License, Version 2.0 (the "License"); 63 | * you may not use this file except in compliance with the License. 64 | * You may obtain a copy of the License at 65 | * 66 | * http://www.apache.org/licenses/LICENSE-2.0 67 | * 68 | * Unless required by applicable law or agreed to in writing, software 69 | * distributed under the License is distributed on an "AS IS" BASIS, 70 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 71 | * See the License for the specific language governing permissions and 72 | * limitations under the License. 73 | * 74 | * @licend The above is the entire license notice for the 75 | * Javascript code in this page 76 | */ 77 | 78 | /** 79 | * @remix-run/router v1.10.0 80 | * 81 | * Copyright (c) Remix Software Inc. 82 | * 83 | * This source code is licensed under the MIT license found in the 84 | * LICENSE.md file in the root directory of this source tree. 85 | * 86 | * @license MIT 87 | */ 88 | 89 | /** 90 | * React Router v6.17.0 91 | * 92 | * Copyright (c) Remix Software Inc. 93 | * 94 | * This source code is licensed under the MIT license found in the 95 | * LICENSE.md file in the root directory of this source tree. 96 | * 97 | * @license MIT 98 | */ 99 | 100 | /** @license React v0.20.2 101 | * scheduler.production.min.js 102 | * 103 | * Copyright (c) Facebook, Inc. and its affiliates. 104 | * 105 | * This source code is licensed under the MIT license found in the 106 | * LICENSE file in the root directory of this source tree. 107 | */ 108 | 109 | /** @license React v16.13.1 110 | * react-is.production.min.js 111 | * 112 | * Copyright (c) Facebook, Inc. and its affiliates. 113 | * 114 | * This source code is licensed under the MIT license found in the 115 | * LICENSE file in the root directory of this source tree. 116 | */ 117 | 118 | /** @license React v17.0.2 119 | * react-dom.production.min.js 120 | * 121 | * Copyright (c) Facebook, Inc. and its affiliates. 122 | * 123 | * This source code is licensed under the MIT license found in the 124 | * LICENSE file in the root directory of this source tree. 125 | */ 126 | 127 | /** @license React v17.0.2 128 | * react-jsx-runtime.production.min.js 129 | * 130 | * Copyright (c) Facebook, Inc. and its affiliates. 131 | * 132 | * This source code is licensed under the MIT license found in the 133 | * LICENSE file in the root directory of this source tree. 134 | */ 135 | 136 | /** @license React v17.0.2 137 | * react.production.min.js 138 | * 139 | * Copyright (c) Facebook, Inc. and its affiliates. 140 | * 141 | * This source code is licensed under the MIT license found in the 142 | * LICENSE file in the root directory of this source tree. 143 | */ 144 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plan measurement", 3 | "author": "Richard Moore", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@ant-design/icons": "^5.2.6", 8 | "@fortawesome/fontawesome-svg-core": "^6.5.1", 9 | "@fortawesome/free-solid-svg-icons": "^6.5.1", 10 | "@fortawesome/react-fontawesome": "^0.2.0", 11 | "@testing-library/jest-dom": "^5.16.2", 12 | "@testing-library/react": "^12.1.3", 13 | "@testing-library/user-event": "^13.5.0", 14 | "antd": "^5.10.2", 15 | "axios": "^1.6.0", 16 | "bootstrap": "^5.3.2", 17 | "buffer": "^6.0.3", 18 | "express": "^4.18.2", 19 | "file-saver": "^2.0.5", 20 | "fs": "^0.0.1-security", 21 | "http-proxy-middleware": "^2.0.6", 22 | "lodash": "^4.17.21", 23 | "react": "^17.0.2", 24 | "react-beautiful-dnd": "^13.1.1", 25 | "react-card-flip": "^1.2.2", 26 | "react-dom": "^17.0.2", 27 | "react-draggable": "^4.4.6", 28 | "react-json-to-excel": "^1.0.7", 29 | "react-pdf": "^5.7.1", 30 | "react-redux": "^8.1.3", 31 | "react-router": "^6.17.0", 32 | "react-router-dom": "^6.17.0", 33 | "react-scripts": "5.0.0", 34 | "react-toastify": "^9.1.3", 35 | "redux": "^4.2.1", 36 | "redux-chunk": "^1.0.11", 37 | "redux-devtools-extension": "^2.13.9", 38 | "redux-logger": "^3.0.6", 39 | "redux-thunk": "^2.4.2", 40 | "sass": "^1.69.4", 41 | "toastr": "^2.1.4", 42 | "uuid": "^9.0.1", 43 | "web-vitals": "^2.1.4", 44 | "xlsx": "^0.18.5" 45 | }, 46 | "scripts": { 47 | "start": "react-scripts start", 48 | "build": "react-scripts build", 49 | "test": "react-scripts test", 50 | "eject": "react-scripts eject" 51 | }, 52 | "eslintConfig": { 53 | "extends": [ 54 | "react-app", 55 | "react-app/jest" 56 | ] 57 | }, 58 | "browserslist": { 59 | "production": [ 60 | ">0.2%", 61 | "not dead", 62 | "not op_mini all" 63 | ], 64 | "development": [ 65 | "last 1 chrome version", 66 | "last 1 firefox version", 67 | "last 1 safari version" 68 | ] 69 | }, 70 | "proxy": "https://localhost:5000" 71 | } 72 | -------------------------------------------------------------------------------- /frontend/public/annotate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/check_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/check_icon.png -------------------------------------------------------------------------------- /frontend/public/document.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/document.pdf -------------------------------------------------------------------------------- /frontend/public/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/download.png -------------------------------------------------------------------------------- /frontend/public/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/example.pdf -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/icon-cursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/public/icon-deduct.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/public/icon-poly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/public/icon-rect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icon-search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/icon-undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 29 | React App 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/main plans for richard.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/main plans for richard.pdf -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/plus_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/plus_icon.png -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/ruler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/ruler.png -------------------------------------------------------------------------------- /frontend/public/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangeYourself0613/Draw_Plan-react/37bc6fcca079d015c3c71f52a36b7aa7ea97210b/frontend/public/spinner.gif -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import MyRoutes from './routes/MyRoutes' 2 | 3 | // Redux 4 | import { Provider } from 'react-redux'; 5 | import store from './store'; 6 | 7 | 8 | 9 | function App() { 10 | return ( 11 |
12 | 13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default App; -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/actions/admin.js: -------------------------------------------------------------------------------- 1 | import api from '../utils/api'; 2 | import { 3 | RETRIEVE_DATA, 4 | } from './types' 5 | 6 | // Retrieve current category from backend 7 | export const retrieveData = () => async (dispatch) => { 8 | 9 | try { 10 | const res = await api.get('/category'); 11 | 12 | dispatch({ 13 | type: RETRIEVE_DATA, 14 | payload: res.data 15 | }); 16 | 17 | } catch (err) { 18 | const errors = err.response.data.errors; 19 | } 20 | }; 21 | 22 | // Create Category 23 | export const createCategory = (data) => async (dispatch) => { 24 | 25 | try { 26 | const res = await api.put('/category', data); 27 | 28 | dispatch({ 29 | type: RETRIEVE_DATA, 30 | payload: res.data 31 | }); 32 | 33 | } catch (err) { 34 | const errors = err.response.data.errors; 35 | } 36 | }; 37 | export const deleteCategory = (id) => async (dispatch) => { 38 | 39 | 40 | try { 41 | const res = await api.delete(`/category/${id}`); 42 | 43 | dispatch({ 44 | type: RETRIEVE_DATA, 45 | payload: res.data 46 | }); 47 | 48 | } catch (err) { 49 | const errors = err.response.data.errors; 50 | } 51 | }; 52 | export const EditSubCategoryById = (id, subcategory) => async (dispatch) => { 53 | try { 54 | const res = await api.post(`/category/${id}`, subcategory); 55 | 56 | dispatch({ 57 | type: RETRIEVE_DATA, 58 | payload: res.data 59 | }); 60 | 61 | } catch (err) { 62 | const errors = err.response.data.errors; 63 | } 64 | }; -------------------------------------------------------------------------------- /frontend/src/actions/alert.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import { SET_ALERT, REMOVE_ALERT } from './types'; 3 | 4 | export const setAlert = (msg, alertType, timeout = 5000) => dispatch => { 5 | const id = uuidv4(); 6 | dispatch({ 7 | type: SET_ALERT, 8 | payload: { msg, alertType, id } 9 | }); 10 | 11 | setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout); 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/actions/auth.js: -------------------------------------------------------------------------------- 1 | import api from '../utils/api'; 2 | import _ from 'lodash'; 3 | import { 4 | REGISTER_SUCCESS, 5 | REGISTER_FAIL, 6 | USER_LOADED, 7 | AUTH_ERROR, 8 | LOGIN_SUCCESS, 9 | LOGIN_FAIL, 10 | LOGOUT 11 | } from './types'; 12 | 13 | /* 14 | NOTE: we don't need a config object for axios as the 15 | default headers in axios are already Content-Type: application/json 16 | also axios stringifies and parses JSON for you, so no need for 17 | JSON.stringify or JSON.parse 18 | */ 19 | 20 | 21 | // Load User 22 | export const loadUser = () => async (dispatch) => { 23 | try { 24 | const res = await api.get('/auth'); 25 | 26 | dispatch({ 27 | type: USER_LOADED, 28 | payload: res.data 29 | }); 30 | } catch (err) { 31 | dispatch({ 32 | type: AUTH_ERROR 33 | }); 34 | } 35 | }; 36 | 37 | // Register User 38 | export const register = (formData) => async (dispatch) => { 39 | const res = await api.post('/users', formData); 40 | if(_.hasIn(res.data, 'token')){ 41 | 42 | dispatch({ 43 | type: REGISTER_SUCCESS, 44 | payload:res.data 45 | }); 46 | 47 | // dispatch(loadUser()); 48 | } 49 | else{ 50 | dispatch({ 51 | type: REGISTER_FAIL, 52 | payload: res.data.errors 53 | }); 54 | } 55 | 56 | }; 57 | 58 | // Login User 59 | export const login = (email, password) => async (dispatch) => { 60 | const body = { email, password }; 61 | 62 | try { 63 | const res = await api.post('/users', body); 64 | console.log('username: ', res.data) 65 | dispatch({ 66 | type: LOGIN_SUCCESS, 67 | payload: res.data 68 | }); 69 | 70 | // dispatch(loadUser()); 71 | } catch (err) { 72 | const errors = err.response.data.errors; 73 | dispatch({ 74 | type: LOGIN_FAIL, 75 | payload: errors 76 | }); 77 | } 78 | }; 79 | 80 | // Logout 81 | export const logout = () => ({ type: LOGOUT }); -------------------------------------------------------------------------------- /frontend/src/actions/result.js: -------------------------------------------------------------------------------- 1 | import api from "../utils/api"; 2 | import { SAVE_RESULT } from "./types"; 3 | 4 | export const saveResult = (data) => async (dispatch) => { 5 | try { 6 | const res = await api.put("/measure", data); 7 | 8 | if (res.data.respond == "success") { 9 | dispatch({ 10 | type: SAVE_RESULT, 11 | payload: "OK", 12 | }); 13 | } else { 14 | console.log(res.msg); 15 | } 16 | } catch (err) { 17 | const errors = err.response.data.errors; 18 | } 19 | }; -------------------------------------------------------------------------------- /frontend/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; 2 | export const REGISTER_FAIL = 'REGISTER_FAIL'; 3 | export const USER_LOADED = 'USER_LOADED'; 4 | export const AUTH_ERROR = 'AUTH_ERROR'; 5 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 6 | export const LOGIN_FAIL = 'LOGIN_FAIL'; 7 | export const LOGOUT = 'LOGOUT'; 8 | export const SET_ALERT = 'SET_ALERT'; 9 | export const REMOVE_ALERT = 'REMOVE_ALERT'; 10 | export const RETRIEVE_DATA = "RETRIEVE_DATA"; 11 | export const SAVE_RESULT = "SAVE_RESULT"; -------------------------------------------------------------------------------- /frontend/src/containers/Admin/Admin.css: -------------------------------------------------------------------------------- 1 | .add_btn { 2 | float: right; 3 | } -------------------------------------------------------------------------------- /frontend/src/containers/Admin/Admin.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Modal, Form, Input, Typography, InputNumber, Popconfirm, FloatButton } from "antd"; 2 | import { Table} from 'antd'; 3 | 4 | import './Admin.css'; 5 | import { useEffect } from "react"; 6 | import {connect} from 'react-redux' 7 | import { useNavigate } from "react-router"; 8 | 9 | import {retrieveData, createCategory, deleteCategory, EditSubCategoryById} from '../../actions/admin'; 10 | import { logout } from "../../actions/auth"; 11 | import { useState } from "react"; 12 | import { LogoutOutlined } from "@ant-design/icons"; 13 | 14 | 15 | {/*=========================================================== */} 16 | {/*============Table Body for SubCategory Creation in New Category Creation ============== */} 17 | 18 | const EditableCell = ({ 19 | editing, 20 | dataIndex, 21 | title, 22 | inputType, 23 | record, 24 | index, 25 | children, 26 | ...restProps 27 | }) => { 28 | const inputNode = inputType === 'number' ? : ; 29 | return ( 30 | 31 | {editing ? ( 32 | 44 | {inputNode} 45 | 46 | ) : ( 47 | children 48 | )} 49 | 50 | ); 51 | }; 52 | 53 | 54 | const Admin = (props) => { 55 | 56 | {/*=========================================================== */} 57 | {/*Get Category Datas from database */} 58 | useEffect(() => { 59 | props.retrieveData() 60 | },[]); 61 | 62 | const navigate = useNavigate(); 63 | 64 | 65 | {/*======================================Category Create & Delete ======================================================*/} 66 | {/*================== Open Create New Category Modal ========================= */} 67 | const [isAddNewCategoryModalOpen, setIsAddNewCategoryModalOpen] = useState(false); 68 | const [inputName, setInputName] = useState(""); 69 | const [inputUnit, setInputUnit] = useState(""); 70 | 71 | const handleAddNewCategory = () => { 72 | setIsAddNewCategoryModalOpen(true); 73 | } 74 | 75 | const handleCreateNewCategory = () => { 76 | const tempObj = { 77 | name: inputName, 78 | unit: inputUnit, 79 | subCategory: data 80 | }; 81 | props.createCategory(tempObj); 82 | setInputName(""); 83 | setInputUnit(""); 84 | setData([]); 85 | setIsAddNewCategoryModalOpen(false); 86 | } 87 | 88 | const handleCancelCreateNewCategoryModal = () => { 89 | setInputName(""); 90 | setInputUnit(""); 91 | setData([]); 92 | setIsAddNewCategoryModalOpen(false); 93 | } 94 | 95 | const handleChangeCategoryName = (e) => { 96 | setInputName(e.target.value); 97 | } 98 | 99 | const handleChangeCategoryUnit = (e) => { 100 | setInputUnit(e.target.value); 101 | } 102 | 103 | const handleDeleteSubCategoryInCreateCategory = (row) => { 104 | console.log('row = ', row); 105 | const {item} = row; 106 | const updatedData = data.filter(value => value.item !== item); 107 | setData(updatedData); 108 | } 109 | 110 | {/*================== Delete Category ========================= */} 111 | 112 | const handleDeleteCategory = (row) => { 113 | const {id} = row; 114 | props.deleteCategory(id); 115 | } 116 | 117 | 118 | {/*=============== Show & Edit SubCategory from Category ============================ */} 119 | const [isSetEditSubCategoryModalOpen, setIsSetEditSubCategoryModalOpen] = useState(false); 120 | const [subCategoryByCategoryName, setSubCategoryByCategoryName] = useState([]); 121 | const [categoryIdToEdit, setCategoryIdToEdit] = useState(""); 122 | 123 | const handleSubCategory = (row) => { 124 | const {id, name} = row; 125 | const item = props.data.find(item => item.name === name); 126 | setCategoryIdToEdit(id); 127 | 128 | if (item) { 129 | const data = JSON.parse(item.subcategory); 130 | const updatedData = data && data.map((element, index) => ({ 131 | ...element, 132 | key: index, 133 | })); 134 | setSubCategoryByCategoryName(updatedData); 135 | 136 | } else { 137 | console.log('Subcategory not found'); 138 | return ""; 139 | } 140 | setIsSetEditSubCategoryModalOpen(true); 141 | } 142 | 143 | const handleEditSubCategory = () => { 144 | props.EditSubCategoryById(categoryIdToEdit, subCategoryByCategoryName ); 145 | setIsSetEditSubCategoryModalOpen(false); 146 | } 147 | 148 | const handleCancelEditSubCategoryModal = () => { 149 | setIsSetEditSubCategoryModalOpen(false); 150 | } 151 | 152 | {/*================================================ */} 153 | {/*===========Edit SubCategory Column */} 154 | 155 | const [subCategoryform] = Form.useForm(); 156 | const [editingSubCategorykey, setEditingSubCategorykey] = useState(''); 157 | const isEditingSubCategory = (record) => record.key === editingSubCategorykey; 158 | const editSubCategory = (record) => { 159 | subCategoryform.setFieldsValue({ 160 | item: '', 161 | price: '', 162 | wastage: '', 163 | ...record, 164 | }); 165 | setEditingSubCategorykey(record.key); 166 | }; 167 | 168 | const cancelEditSubCategory = () => { 169 | setEditingSubCategorykey(''); 170 | }; 171 | 172 | const saveEditSubCategory = async (key) => { 173 | try { 174 | const row = await subCategoryform.validateFields(); 175 | const newData = [...subCategoryByCategoryName]; 176 | const index = newData.findIndex((item) => key === item.key); 177 | if (index > -1) { 178 | const item = newData[index]; 179 | newData.splice(index, 1, { 180 | ...item, 181 | ...row, 182 | }); 183 | setSubCategoryByCategoryName(newData); 184 | setEditingSubCategorykey(''); 185 | } else { 186 | newData.push(row); 187 | setSubCategoryByCategoryName(newData); 188 | setEditingSubCategorykey(''); 189 | } 190 | } catch (errInfo) { 191 | console.log('Validate Failed:', errInfo); 192 | } 193 | }; 194 | 195 | const SubCategory_columns = [ 196 | { 197 | title: 'Item', 198 | dataIndex: 'item', 199 | width: '30%', 200 | editable: true, 201 | }, 202 | { 203 | title: 'Price($)', 204 | dataIndex: 'price', 205 | width: '10%', 206 | editable: true, 207 | }, 208 | { 209 | title: 'Wastage(%)', 210 | dataIndex: 'wastage', 211 | width: '10%', 212 | editable: true, 213 | }, 214 | { 215 | title: 'operation', 216 | dataIndex: 'operation', 217 | width:'20%', 218 | render: (_, record) => { 219 | const editable = isEditingSubCategory(record); 220 | return editable ? ( 221 | 222 | saveEditSubCategory(record.key)} 224 | style={{ 225 | marginRight: 8, 226 | }} 227 | > 228 | Save 229 | 230 | 231 | Cancel 232 | 233 | 234 | ) : ( 235 | editSubCategory(record)}> 236 | Edit 237 | 238 | ); 239 | }, 240 | }, 241 | { 242 | title: 'Delete', 243 | dataIndex: 'delete', 244 | width:"30%", 245 | render: (_, row) => ( 246 | 249 | ), 250 | }, 251 | ]; 252 | 253 | const edit_SubCategory_columns = SubCategory_columns.map((col) => { 254 | if (!col.editable) { 255 | return col; 256 | } 257 | return { 258 | ...col, 259 | onCell: (record) => ({ 260 | record, 261 | inputType: col.dataIndex === 'item' ? 'text' : 'number', 262 | dataIndex: col.dataIndex, 263 | title: col.title, 264 | editing: isEditingSubCategory(record), 265 | }), 266 | }; 267 | }); 268 | 269 | const handleAddmoreSubCategoryButton = () => { 270 | let newkey = subCategoryByCategoryName.length; 271 | const newData = [...subCategoryByCategoryName]; 272 | const row = { 273 | key: newkey, 274 | item: "", 275 | price:0, 276 | wastage:0, 277 | } 278 | newData.push(row); 279 | setSubCategoryByCategoryName(newData); 280 | setEditingSubCategorykey(newkey); 281 | } 282 | 283 | const handleDeleteSubCategoryInCategory = (row) => { 284 | const {item} = row; 285 | const updatedsubCategory = subCategoryByCategoryName.filter(value => value.item !== item); 286 | setSubCategoryByCategoryName(updatedsubCategory); 287 | } 288 | 289 | 290 | {/*=========================================================================== */} 291 | {/*=============Create New Catetory Column================= */} 292 | const Category_columns = [ 293 | { 294 | title: 'Category', 295 | dataIndex: 'name', 296 | key: 'name', 297 | }, 298 | { 299 | title: 'Unit of Measure', 300 | dataIndex: 'unit', 301 | key: 'unit', 302 | }, 303 | { 304 | title: 'SubCategory', 305 | dataIndex: 'subcategory', 306 | key: 'subcategory', 307 | render: (_, row) => ( 308 | 309 | ), 310 | }, 311 | { 312 | title: 'Delete', 313 | dataIndex: 'delete', 314 | key: 'delete', 315 | render: (_, row) => ( 316 | 317 | ) 318 | } 319 | ]; 320 | 321 | 322 | 323 | {/*======================================================================= */} 324 | {/*=========Create, Edit, SubCategory in Create New Category Modal */} 325 | 326 | const [form] = Form.useForm(); 327 | const [data, setData] = useState([]); 328 | const [editingKey, setEditingKey] = useState(''); 329 | const isEditing = (record) => record.key === editingKey; 330 | const edit = (record) => { 331 | form.setFieldsValue({ 332 | item: '', 333 | price: '', 334 | wastage: '', 335 | ...record, 336 | }); 337 | setEditingKey(record.key); 338 | }; 339 | const cancel = () => { 340 | setEditingKey(''); 341 | }; 342 | const save = async (key) => { 343 | try { 344 | const row = await form.validateFields(); 345 | const newData = [...data]; 346 | const index = newData.findIndex((item) => key === item.key); 347 | if (index > -1) { 348 | const item = newData[index]; 349 | newData.splice(index, 1, { 350 | ...item, 351 | ...row, 352 | }); 353 | setData(newData); 354 | setEditingKey(''); 355 | } else { 356 | newData.push(row); 357 | setData(newData); 358 | setEditingKey(''); 359 | } 360 | } catch (errInfo) { 361 | console.log('Validate Failed:', errInfo); 362 | } 363 | }; 364 | const columns = [ 365 | { 366 | title: 'Item', 367 | dataIndex: 'item', 368 | width: '20%', 369 | editable: true, 370 | }, 371 | { 372 | title: 'Price($)', 373 | dataIndex: 'price', 374 | width: '15%', 375 | editable: true, 376 | }, 377 | { 378 | title: 'Wastage(%)', 379 | dataIndex: 'wastage', 380 | width: '15%', 381 | editable: true, 382 | }, 383 | { 384 | title: 'operation', 385 | dataIndex: 'operation', 386 | width:'40%', 387 | render: (_, record) => { 388 | const editable = isEditing(record); 389 | return editable ? ( 390 | 391 | save(record.key)} 393 | style={{ 394 | marginRight: 8, 395 | }} 396 | > 397 | Save 398 | 399 | 400 | Cancel 401 | 402 | 403 | ) : ( 404 | edit(record)}> 405 | Edit 406 | 407 | ); 408 | }, 409 | }, 410 | { 411 | title: 'Delete', 412 | dataIndex: 'delete', 413 | render:(_, row) => ( 414 | 415 | ) 416 | } 417 | ]; 418 | const mergedColumns = columns.map((col) => { 419 | if (!col.editable) { 420 | return col; 421 | } 422 | return { 423 | ...col, 424 | onCell: (record) => ({ 425 | record, 426 | inputType: col.dataIndex === 'item' ? 'text' : 'number', 427 | dataIndex: col.dataIndex, 428 | title: col.title, 429 | editing: isEditing(record), 430 | }), 431 | }; 432 | }); 433 | 434 | const handleAddmoreButton = () => { 435 | let newkey = data.length; 436 | const newData = [...data]; 437 | const row = { 438 | key: newkey, 439 | item: "", 440 | price:0, 441 | wastage:0, 442 | } 443 | newData.push(row); 444 | setData(newData); 445 | setEditingKey(newkey); 446 | } 447 | 448 | 449 | return ( 450 |
451 | } 453 | type="primary" 454 | style={{ 455 | right: "10%", 456 | }} 457 | onClick={() => { 458 | props.logout(); 459 | navigate('/signin', {replace:true}) 460 | }} 461 | tooltip="Log out" 462 | /> 463 |
464 | 465 |
466 |
467 | 468 | 469 | 470 | 476 |
477 | Edit SubCategory 478 |
479 |
491 | 492 | 493 | 494 | 495 | 496 | 503 |
504 | Category Items 505 | 511 |
512 |
513 | Type Of measure 514 | 520 |
521 |
522 | SubCategory 523 |
524 |
536 | 537 | 538 | 539 | 540 | 541 | ) 542 | } 543 | 544 | const mapStateToProps = (state) => ({ 545 | data: state.admin.data, 546 | }) 547 | 548 | export default connect(mapStateToProps, {retrieveData, createCategory, deleteCategory, EditSubCategoryById, logout})(Admin) 549 | 550 | 551 | -------------------------------------------------------------------------------- /frontend/src/containers/Construction/DraggableToolbar/DraggableToolbar.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Draggable from "react-draggable" 3 | 4 | const DraggableToolbar = ({onClick}) => { 5 | 6 | const [drawType, setDrawType] = useState(""); 7 | const handleDrawType = (type) => { 8 | setDrawType(type); 9 | onClick(type); 10 | } 11 | 12 | return ( 13 | 14 |
15 |
handleDrawType("cursor")}> 16 | cursor icon 17 |
18 |
handleDrawType("rect")} 22 | > 23 | rect icon 24 |

Rectangle

25 |
26 |
handleDrawType("poly")}> 27 | poly icon 28 |

Polygon

29 |
30 | 31 |
handleDrawType("line")}> 32 | length icon 33 |

Length

34 |
35 |
handleDrawType("deduct")}> 36 | deduct icon 37 |

Deduct

38 |
39 |
handleDrawType("dot")}> 40 | count icon 41 |

Count

42 |
43 |
handleDrawType("undo")}> 44 | undo icon 45 |

Undo

46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | export default DraggableToolbar; -------------------------------------------------------------------------------- /frontend/src/containers/Construction/DraggableWidget/DraggableWidget.js: -------------------------------------------------------------------------------- 1 | import { Input, Button, notification, Select, Table, Dropdown, Menu } from "antd"; 2 | import { SmileOutlined } from "@ant-design/icons"; 3 | import Modal from "antd/es/modal/Modal"; 4 | import { useEffect } from "react"; 5 | import {connect, useSelector} from 'react-redux'; 6 | import { useState } from "react"; 7 | import Draggable from "react-draggable"; 8 | import { exportToExcel } from 'react-json-to-excel'; 9 | 10 | import {saveResult} from '../../../actions/result'; 11 | 12 | 13 | const DraggableWidget = (props) => { 14 | const [data, setData] =useState([]); 15 | const [isOpenModal, setIsOpenModal] = useState(false); 16 | const [selectedData, setSelectedData] = useState(null); 17 | const [isSaveDatabaseConfirmModal, setIsSaveDatabaseConfirmModal] = useState(false); 18 | const [previousmeasure, setPreviousMeasure] = useState(0); 19 | const [height, setHeight] = useState(1); 20 | const [depth, setDepth] = useState(1); 21 | const [pitch, setPitch] = useState(0); 22 | const [showAllData, setShowAllData] = useState(false); 23 | 24 | const [selectedIndex, setSelectedIndex] = useState(0); 25 | 26 | const [api, contextHolder] = notification.useNotification(); 27 | 28 | const openNotification = () => { 29 | api.open({ 30 | message: 'Success', 31 | description: 32 | 'You have successfully saved measure data to the database.', 33 | icon: ( 34 | 39 | ), 40 | }); 41 | }; 42 | 43 | useEffect(() => { 44 | let temp = props.widgetData.slice(1).map((item, index) => { 45 | return {key: index, ...item} 46 | }) 47 | console.log('temp: ', temp); 48 | temp.projectName = props.projectName; 49 | setData(temp) 50 | },[props.widgetData]) 51 | 52 | useEffect(() => { 53 | if(props.data === "OK"){ 54 | openNotification() 55 | } 56 | },[props.data]) 57 | 58 | const handleDimension = (index) => { 59 | console.log(index); 60 | const selectedRow = data[index]; 61 | setSelectedIndex(index); 62 | setSelectedData(selectedRow); 63 | setPreviousMeasure(selectedRow && selectedRow.measure); 64 | setIsOpenModal(true); 65 | }; 66 | 67 | const onOK = () => { 68 | const newData = [...data]; 69 | newData[selectedIndex] = selectedData; 70 | setData(newData); 71 | setIsOpenModal(false); 72 | } 73 | 74 | const onCancel = () => { 75 | setIsOpenModal(false); 76 | } 77 | 78 | 79 | const onDepthChange = (e) => { 80 | if(e.target.value) 81 | {setDepth(e.target.value);} 82 | else {setDepth(1);} 83 | 84 | setSelectedData({ 85 | ...selectedData, 86 | result: previousmeasure * height * (e.target.value ? e.target.value : 1) *(1 + pitch / 100) 87 | }) 88 | selectedData.result = selectedData.result 89 | } 90 | 91 | const onHeightChange = (e) => { 92 | if(e.target.value) 93 | setHeight(e.target.value); 94 | else 95 | setHeight(1); 96 | setSelectedData({ 97 | ...selectedData, 98 | result:previousmeasure * depth * (e.target.value ? e.target.value : 1) * (1 + pitch / 100) 99 | }) 100 | } 101 | 102 | const onPitchChange = (e) => { 103 | if(e.target.value) setPitch(e.target.value); 104 | else setPitch(0); 105 | setSelectedData({ 106 | ...selectedData, 107 | result:previousmeasure * depth * height * (1 + e.target.value / 100) 108 | }) 109 | } 110 | 111 | const saveDatabase = () => { 112 | setIsSaveDatabaseConfirmModal(true); 113 | } 114 | 115 | const handleSaveData = () => { 116 | props.saveResult(data); 117 | setIsSaveDatabaseConfirmModal(false); 118 | } 119 | 120 | const handleSaveCancel = () => { 121 | setIsSaveDatabaseConfirmModal(false); 122 | } 123 | 124 | const ExportExcel = () => { 125 | 126 | const transformedData = []; 127 | 128 | console.log('testData = ', data); 129 | 130 | for(var i = 0; i< data.length; i++) { 131 | for(var j = 0; j < data[i].subcategory.length; j++) { 132 | let temp = { 133 | area: data[i].area, 134 | subarea: data[i].subarea, 135 | category: data[i].category, 136 | subcategory: data[i].subcategory[j], 137 | type: data[i].type, 138 | measure: data[i].measure, 139 | unit: data[i].unit, 140 | price: data[i].price[j] 141 | } 142 | transformedData.push(temp); 143 | console.log("trans = ", transformedData); 144 | } 145 | } 146 | 147 | exportToExcel(transformedData, 'measure'); 148 | } 149 | 150 | const widgetdata_columns = [ 151 | { 152 | title: 'Area', 153 | dataIndex: 'area', 154 | key: 'area', 155 | }, 156 | { 157 | title: 'SubArea', 158 | dataIndex: 'subarea', 159 | key: 'subarea', 160 | }, 161 | { 162 | title: 'Category', 163 | dataIndex: 'category', 164 | key: 'category' 165 | }, 166 | { 167 | title: 'SubCategory', 168 | dataIndex: 'subcategory', 169 | key: 'subcategory', 170 | render: (subcategory, record) => ( 171 | 181 | ) 182 | }, 183 | { 184 | title: 'Type', 185 | dataIndex: 'type', 186 | key: 'type' 187 | }, 188 | { 189 | title: 'Unit', 190 | dataIndex: 'unit', 191 | key: 'unit' 192 | }, 193 | { 194 | title: 'Measure', 195 | dataIndex: 'measure', 196 | key: 'measure' 197 | }, 198 | { 199 | title: 'HDP', 200 | dataIndex: 'hdp', 201 | key: 'hdp', 202 | render:(_, row) => ( 203 | //
props.onClick(row)}>HDP
204 |
handleDimension(row.key)}>HDP
205 | ) 206 | }, 207 | { 208 | title:'Result', 209 | dataIndex: 'result', 210 | key: 'result' 211 | }, 212 | { 213 | title: 'Price($)', 214 | dataIndex: 'price', 215 | key: 'price', 216 | render:(price, record) => ( 217 | price[selectedIndex] 218 | ) 219 | }, 220 | { 221 | title: 'Option', 222 | dataIndex: 'option', 223 | key: 'option', 224 | render: (record, row) => ( 225 | 226 | ) 227 | } 228 | 229 | ]; 230 | 231 | const { Option } = Select; 232 | const handleSubCategorySelect = (value, option) => { 233 | setSelectedIndex(option.key); 234 | } 235 | 236 | const [selectedRowKeys, setSelectedRowKeys] = useState([]); 237 | 238 | const onSelectChange = (newSelectedRowKeys) => { 239 | let selectedID = newSelectedRowKeys.map(index => ( 240 | data[index].id 241 | )) 242 | props.selectData(selectedID); 243 | setSelectedRowKeys(newSelectedRowKeys); 244 | }; 245 | const rowSelection = { 246 | selectedRowKeys, 247 | onChange: onSelectChange, 248 | }; 249 | 250 | const dataSource = showAllData ? data : data.slice(-1); 251 | const handleDropdownMenuClick = (e) => { 252 | if(e.key === "showAll") setShowAllData(true); 253 | else setShowAllData(false); 254 | }; 255 | 256 | const dropdownMenu = ( 257 | 258 | Show All Data 259 | Show Only Last Data 260 | 261 | ); 262 | 263 | 264 | 265 | return ( 266 | <> 267 | {contextHolder} 268 | 269 |
270 | 271 | 272 | 273 |
279 |
280 | 281 | 282 |
283 | 284 | 285 | 286 | 292 | { 293 | selectedData && ( 294 |
295 |
296 |
{selectedData.area}
297 |
{selectedData.subarea}
298 |
{selectedData.category}
299 |
{selectedData.type}
300 |
{selectedData.measure}
301 |
302 |
303 |
304 |
305 | 306 | 312 |
313 |
314 | 315 | 321 |
322 |
323 | 324 | 330 |
331 |
332 |
333 |
334 |
Total
335 |
336 |
{selectedData.result}
337 |
338 |
339 | ) 340 | } 341 | 342 |
343 | 344 |

Are you really going to save Drawing Data to Database?

345 |
346 | 347 | 348 | 349 | ) 350 | } 351 | 352 | const mapStateToProps = (state) => ({ 353 | data: state.result.data 354 | }) 355 | 356 | export default connect(mapStateToProps, {saveResult})(DraggableWidget) ; -------------------------------------------------------------------------------- /frontend/src/containers/Construction/Main/Main.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap"); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | font-family: "Roboto", sans-serif; 7 | } 8 | .modal-footer { 9 | padding: 0; 10 | } 11 | 12 | #upload_container{ 13 | width: 200px; 14 | height: 200px; 15 | margin: 0 auto; 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | margin-top: 200px; 20 | background-color: rgb(228, 231, 237); 21 | border: 1px dashed #ccc; 22 | } 23 | .main { 24 | padding: 10px; 25 | } 26 | #old_project { 27 | width: 100%; 28 | height: 200px; 29 | margin: 0 auto; 30 | align-items: center; 31 | align-content: center; 32 | margin-top: 200px; 33 | border: 1px dashed #ccc; 34 | text-align: center; 35 | font-size: large; 36 | font-weight: bold; 37 | /* display: flex; */ 38 | } 39 | 40 | .old_project_edit { 41 | margin-top: 30px; 42 | cursor: pointer; 43 | font-size: 24px; 44 | } 45 | 46 | .old_project_edit:hover { 47 | color: aqua; 48 | } 49 | 50 | .old_project_name { 51 | margin-top: 10px; 52 | font-size: 2rem; 53 | } 54 | 55 | #newproject-container { 56 | height: 100%; 57 | margin: 0 auto; 58 | display: flex; 59 | align-items: center; 60 | margin-top: 200px; 61 | align-items: center; 62 | width: 100%; 63 | border: 1px dashed #ccc; 64 | } 65 | .custom-file-upload{ 66 | width: 100%; 67 | cursor: pointer; 68 | } 69 | 70 | #set-scale { 71 | background-color: rgb(245, 245, 245); 72 | position: fixed; 73 | bottom: 0; 74 | width: 80%; 75 | height: 80vh; 76 | } 77 | 78 | #upload_file { 79 | margin: auto; 80 | display: flex; 81 | flex-direction: column; 82 | cursor: pointer; 83 | width: 150px; 84 | height: 100px; 85 | } 86 | .page { 87 | /* display: flex; 88 | flex-direction: column; 89 | justify-content: center; */ 90 | align-items: center; 91 | margin: 0 auto; 92 | height: 100%; 93 | } 94 | 95 | .main-main { 96 | display: flex; 97 | flex-direction: row-reverse; 98 | align-items: center; 99 | justify-content: space-between; 100 | row-gap: 1px; 101 | flex-basis: 150px; 102 | } 103 | 104 | .pagelist { 105 | display: flex; 106 | flex-direction: row; 107 | } 108 | 109 | #listpage { 110 | flex: 1; 111 | height: 70vh; 112 | overflow: auto; 113 | } 114 | 115 | #set-scale { 116 | flex-shrink: 0; 117 | width: 200px; 118 | height: 30vh; 119 | padding-left: 10px; 120 | } 121 | 122 | #scale { 123 | text-align: center; 124 | } 125 | 126 | .page_setting { 127 | display: flex; 128 | align-items: center; 129 | } 130 | 131 | .scale{ 132 | width: 40%; 133 | height: 20%; 134 | } 135 | .page_setting > label { 136 | size: 5px; 137 | } 138 | 139 | .draggbletoolbar { 140 | display: flex; 141 | background: white; 142 | border-radius: 7px; 143 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 144 | width: 23%; 145 | margin: 10px; 146 | padding: 5px; 147 | float: left; 148 | position: fixed; 149 | z-index: 999; 150 | } 151 | 152 | .draggableWidget { 153 | display: flex; 154 | flex-direction: column; 155 | background: lawngreen; 156 | border-radius: 7px; 157 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; 158 | margin: 10px; 159 | right: 10px; 160 | top: 10px; 161 | position: fixed; 162 | z-index: 999; 163 | } 164 | 165 | .widget-header { 166 | display: flex; 167 | background: #D3D5DB; 168 | border-radius: 3px; 169 | width: 100%; 170 | border-bottom: 1px solid rgba(120, 120, 150, 0.2); 171 | } 172 | 173 | .draggable { 174 | width: 100%; 175 | margin: 0; 176 | padding: 0; 177 | } 178 | 179 | .widget-content { 180 | display: flex; 181 | background: #E6E7EB; 182 | border-radius: 3px; 183 | width: 100%; 184 | border-bottom: 1px solid rgba(120, 120, 150, 0.2); 185 | } 186 | 187 | /* .draggableWidget div { 188 | margin: 0; 189 | padding: 0; 190 | text-align: center; 191 | border-right:1px solid rgba(120, 120, 150, 0.2); 192 | border-bottom: 1px solid rgba(120, 120, 150, 0.2); 193 | } */ 194 | 195 | .btn-toolbar { 196 | border-radius: 10%; 197 | margin: 5px; 198 | font-size: 9px; 199 | width: 50px; 200 | height: 50px; 201 | /* border-color: #000; */ 202 | place-content: center; 203 | } 204 | 205 | .type { 206 | background-color: lightcoral; 207 | } 208 | 209 | .type-active { 210 | background-color: white; 211 | } 212 | 213 | .btn-toolbar:hover{ 214 | background-color: #C5C8D3; 215 | } 216 | 217 | .btn-toolbar:focus{ 218 | background-color: red; 219 | } 220 | 221 | .btn-toolbar > img { 222 | width: 20px; 223 | height: 20px; 224 | } 225 | 226 | .btn-toolbar > p { 227 | margin: 0; 228 | padding: 0; 229 | background-color: transparent; 230 | } 231 | 232 | .pageviewer { 233 | position: relative; 234 | justify-content: center; 235 | margin: auto; 236 | overflow: auto; 237 | } 238 | 239 | .perpage { 240 | margin-top: 10px; 241 | padding: 0px 10px; 242 | cursor: pointer; 243 | } 244 | 245 | .perpage:focus { 246 | border: solid 1px #444444; 247 | } 248 | 249 | .loadedpage { 250 | background-color: lightblue; 251 | } 252 | 253 | .pagenumber { 254 | text-align: center; 255 | margin: 0; 256 | padding: inherit; 257 | } 258 | 259 | nav { 260 | display: flex; 261 | margin: 1rem auto; 262 | align-items: center; 263 | justify-content: space-between; 264 | } 265 | 266 | button { 267 | background-color: #f5f5f5; 268 | border: none; 269 | color: #000; 270 | padding: 1rem; 271 | list-style: none; 272 | font-size: 1.3rem; 273 | margin: 0.7rem; 274 | border-radius: 20%; 275 | cursor: pointer; 276 | } 277 | 278 | p { 279 | font-size: 1.3rem; 280 | background-color: #f5f5f5; 281 | padding: 1rem; 282 | } 283 | 284 | #measure { 285 | position: fixed; 286 | top: 0; 287 | left: 0; 288 | color: black; 289 | background-color: transparent; 290 | font-family: Arial, sans-serif; 291 | font-size: 24px; 292 | font-weight: 800; 293 | z-index: 999; 294 | pointer-events: none; 295 | } 296 | 297 | .header, .sub-header, .category, .subcategory { 298 | margin: 5px auto; 299 | } 300 | 301 | .widget-footer { 302 | background-color: gray; 303 | text-align: right; 304 | } 305 | -------------------------------------------------------------------------------- /frontend/src/containers/Construction/Main/Main2.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { connect } from 'react-redux'; 4 | import { Modal, Select, Input, FloatButton, Spin, ConfigProvider, theme } from "antd"; 5 | import { LogoutOutlined, PlusOutlined, ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons'; 6 | import { Document, Page, pdfjs } from "react-pdf/dist/esm/entry.webpack"; 7 | import { retrieveData } from "../../../actions/admin"; 8 | import { logout } from "../../../actions/auth"; 9 | import { v4 as uuidv4 } from 'uuid'; 10 | 11 | import Scale from "../Scale/Scale"; 12 | import DraggableToolbar from "../DraggableToolbar/DraggableToolbar"; 13 | import DraggableWidget from "../DraggableWidget/DraggableWidget"; 14 | import {Spinner} from '../../../utils/Spinner'; 15 | 16 | import "react-pdf/dist/esm/Page/AnnotationLayer.css"; 17 | import "bootstrap/dist/css/bootstrap.min.css"; 18 | import "bootstrap/dist/js/bootstrap.bundle.min"; 19 | import "./Main.css"; 20 | 21 | pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; 22 | 23 | const App = (props) => { 24 | 25 | const { Option } = Select; 26 | 27 | let prevs = []; 28 | 29 | const navigate = useNavigate(); 30 | 31 | // =================== Use Ref Making for Canvas =================== 32 | const canvasRef = useRef(null); 33 | const annotationLayerRef = useRef(null); 34 | 35 | // =================== File Upload =================== 36 | 37 | const [file, setFile] = useState(null); 38 | const [numPages, setNumPages] = useState(null); 39 | const [pageNumber, setPageNumber] = useState(1); 40 | 41 | // ======== Set Page List Section Setting ====== 42 | 43 | const [listWidth, setlistWidth] = useState(null); 44 | const [listHeight, setlistHeight] = useState(null); 45 | const listRef = useRef(null); 46 | 47 | // ======== Set Canvas Section Setting ====== 48 | 49 | const [canvasWidth, setcanvasWidth] = useState(null); 50 | 51 | // ======== Set widget Data Store ====== 52 | const [widgetData, setWidgetData] = useState([ 53 | { 54 | area: '', 55 | subarea: '', 56 | category: '', 57 | subcategory: [], 58 | type: "", 59 | unit: '', 60 | measure: 0.0, 61 | result: 0.0, 62 | price: 0.0, 63 | deductRect: [], 64 | } 65 | ]); 66 | 67 | // ======== Set Page Setting ====== 68 | 69 | const [pageScale, setPageScale] = useState("100"); 70 | const [selectScaleValue, setSelectScaleValue] = useState("100"); 71 | 72 | const [pageSize, setPageSize] = useState("A1"); 73 | const [selectSizeValue, setSelectSizeValue] = useState("A1"); 74 | 75 | // ======== Set Single/Double Click ====== 76 | const [clickTimer, setClickTimer] = useState(null); 77 | 78 | // ======== Set Modal Open ====== 79 | const [isSetScaleModalOpen, setIsSetScaleModalOpen] = useState(false); 80 | const [isChangeScaleModalOpen, setIsChangeScaleModalOpen] = useState(false); 81 | 82 | // ======= Drawing Start Point =============== 83 | const [startPoint, setStartPoint] = useState({}); 84 | 85 | // ========= Drawing Possible ========== 86 | const [isDrawing, setIsDrawing] = useState(false); 87 | 88 | // ========= Value Display Possible ========== 89 | const [isDisplayValue, setIsDisplayValue] = useState(false); 90 | const [displayValue, setDisplayValue] = useState(0); 91 | 92 | // ======= Mouse Position ============== 93 | const [mousePointX, setMousePointX] = useState(0); 94 | const [mousePointY, setMousePointY] = useState(0); 95 | 96 | // =============== Layout Size ================ 97 | const [layoutSize, setLayoutSize] = useState(0); 98 | 99 | // ============= Zoom Scale Setting ========== 100 | const [zoomScale, setZoomScale] = useState(1); 101 | 102 | // ======== Draw Type =============== 103 | const [drawType, setDrawType] = useState(null); 104 | 105 | // ========= Polygon drawing Start Possible ========== 106 | // const [isPolyStart, setIsPolyStart] = useState(false); 107 | 108 | // =========== Polygon dots Tracker =============== 109 | const [polyTracker, setPolyTracker] = useState([]); 110 | 111 | // ============= PolyLine Dots tracker =========== 112 | const [polyLineTracker, setPolyLineTracker] = useState([]); 113 | 114 | // ========== Previous Shape Drawing Store ================ 115 | const [prevState, setPrevState] = useState([]); 116 | 117 | // ============ Label Set Modal Open ================ 118 | const [isSetLabelOpen, setIsSetLabelOpen] = useState(false); 119 | 120 | // ============= Category Select List =============== 121 | const [categorySelectList, SetCategorySelectList] = useState([]); 122 | const [subCategorySelectList, setSubCategorySelectList] = useState([]); 123 | 124 | // ===================== Set Label Values ================ 125 | const [labelheader, setLabelHeader] = useState(""); 126 | const [labelsubheader, setLabelSubHeader] = useState(""); 127 | const [labelcategory, setLabelCategory] = useState(""); 128 | const [labelsubcategory, setLabelSubCategory] = useState([]); 129 | 130 | 131 | // ========= Measure Value============= 132 | const [previouslength, setPreviousLegth] = useState(0); 133 | 134 | const [dotNum, setDotNum] = useState(0); 135 | 136 | const [selectedID, setSelectedID] = useState([]); 137 | 138 | const [isLoading, setIsLoading] = useState(false); 139 | 140 | // ==================== UseEffect ========================== 141 | 142 | useEffect(() => { 143 | const canvas = document.getElementById("pdf-canvas"); 144 | 145 | if (canvas) { 146 | drawAnnotations(); 147 | } 148 | setPrevState([]) 149 | 150 | }, [file, pageNumber]); 151 | 152 | useEffect(() => { 153 | return () => { 154 | clearTimeout(clickTimer); 155 | }; 156 | }, [clickTimer]); 157 | 158 | useEffect(() => { 159 | props.retrieveData(); 160 | },[]); 161 | 162 | useEffect(() => { 163 | const listElement = listRef.current; 164 | if (listElement) { 165 | const { width, height } = listElement.getBoundingClientRect(); 166 | setlistWidth(width * 0.9); 167 | setlistHeight(height * 0.9); 168 | } 169 | }, [file]); 170 | 171 | useEffect(() => { 172 | if(annotationLayerRef.current) redraw(); 173 | },[prevState, selectedID]) 174 | 175 | // ============================================ 176 | // Page Setting function 177 | 178 | 179 | const onhandleSetPageScaleSelect = (e) => { 180 | setSelectScaleValue(e.target.value); 181 | } 182 | 183 | const onhandleSetPageSizeSelect = (e) => { 184 | setSelectSizeValue(e.target.value); 185 | } 186 | 187 | const onOkSetScaleModal = () => { 188 | const categorylist = props.data && props.data.map((item) => {return item.name}); 189 | SetCategorySelectList(categorylist); 190 | setPageScale(selectScaleValue); 191 | setPageSize(selectSizeValue); 192 | switch(selectSizeValue) { 193 | case "A0": 194 | setLayoutSize(1189); 195 | break; 196 | case "A1": 197 | setLayoutSize(841); 198 | break; 199 | case "A2": 200 | setLayoutSize(594); 201 | break; 202 | case "A3": 203 | setLayoutSize(420); 204 | break; 205 | case "A4": 206 | setLayoutSize(297); 207 | break; 208 | case "A5": 209 | setLayoutSize(210); 210 | break; 211 | case "A6": 212 | setLayoutSize(148.5); 213 | break; 214 | default: 215 | break; 216 | } 217 | setIsSetScaleModalOpen(false); 218 | } 219 | 220 | const onOkChangeScaleModal = () => { 221 | setPageScale(selectScaleValue); 222 | setIsChangeScaleModalOpen(false); 223 | } 224 | 225 | const onCloseSetScaleModal = () => { 226 | setIsSetScaleModalOpen(false); 227 | } 228 | 229 | const onCloseChangeScaleModal = () => { 230 | setIsChangeScaleModalOpen(false); 231 | } 232 | 233 | const handleCategorySelect = (event) => { 234 | setLabelCategory(event.target.value); 235 | const seletedCategoryData = props.data.filter(item => item.name === event.target.value); 236 | const subcategorylist = JSON.parse(seletedCategoryData[0].subcategory); 237 | const list = subcategorylist.map((index) => { 238 | return {value: index.item, label: index.item}; 239 | }) 240 | setSubCategorySelectList(list); 241 | }; 242 | 243 | const handleSubCategorySelect = (selectedOptions) => { 244 | setLabelSubCategory(selectedOptions); 245 | } 246 | 247 | const onheaderChange = (e) => { 248 | setLabelHeader(e.target.value); 249 | } 250 | 251 | const onSubheaderChange = (e) => { 252 | setLabelSubHeader(e.target.value); 253 | } 254 | 255 | const onOKSetLabelModal = () => { 256 | setIsSetLabelOpen(false); 257 | } 258 | 259 | const onCancelSetLabelModal = () => { 260 | setLabelHeader(""); 261 | setLabelSubHeader(""); 262 | setLabelCategory(""); 263 | setLabelSubCategory(""); 264 | setIsSetLabelOpen(false); 265 | } 266 | 267 | // ==========================List and Viewer Component Size Setting==================================== 268 | 269 | const drawAnnotations = async () => { 270 | const fileReader = new FileReader(); 271 | let uint8Array = new Uint8Array(); 272 | 273 | fileReader.onload = () => { 274 | const arrayBuffer = fileReader.result; 275 | uint8Array = new Uint8Array(arrayBuffer); 276 | 277 | renderPdf(uint8Array); 278 | } 279 | setIsSetScaleModalOpen(true); 280 | fileReader.readAsArrayBuffer(file); 281 | }; 282 | 283 | 284 | // =======================File Upload================================== 285 | 286 | const handleDragOver = (event) => { 287 | event.preventDefault(); 288 | } 289 | 290 | const handleDrop = (e) => { 291 | e.preventDefault(); 292 | const seletedfile = e.dataTransfer.files[0]; 293 | setFile(seletedfile); 294 | setPageNumber(1); 295 | } 296 | 297 | const onFileChange = (e) => { 298 | const selectedfile = e.target.files[0] 299 | setFile(selectedfile); 300 | setPageNumber(1); 301 | } 302 | 303 | const onDocumentLoadSuccess = ({ numPages }) => { 304 | setNumPages(numPages); 305 | }; 306 | 307 | const renderPdf = async (data) => { 308 | const pdfjs = await import("pdfjs-dist/build/pdf"); 309 | 310 | pdfjs.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; 311 | 312 | setIsLoading(true); 313 | 314 | const loadingTask = pdfjs.getDocument(data); 315 | const pdf = await loadingTask.promise; 316 | 317 | const page = await pdf.getPage(pageNumber); 318 | 319 | const scale = 1; 320 | const viewport = page.getViewport({ scale }); 321 | 322 | const canvas = canvasRef.current; 323 | const context = canvas.getContext("2d"); 324 | 325 | canvas.width = viewport.width; 326 | canvas.height = viewport.height; 327 | 328 | setcanvasWidth(canvas.width); 329 | 330 | const annotationLayer = annotationLayerRef.current; 331 | annotationLayer.width = viewport.width; 332 | annotationLayer.height = viewport.height; 333 | 334 | const renderContext = { 335 | canvasContext: context, 336 | viewport, 337 | }; 338 | 339 | 340 | await page.render(renderContext).promise; 341 | 342 | setIsLoading(false); 343 | }; 344 | 345 | // ===================Toolbar Setting================================== 346 | 347 | const settingDrawType = (type) => { 348 | switch(type) { 349 | case "cursor": 350 | break; 351 | case "rect": 352 | setIsSetLabelOpen(true); 353 | break; 354 | case "poly": 355 | setIsSetLabelOpen(true); 356 | break; 357 | case "line": 358 | setIsSetLabelOpen(true); 359 | break; 360 | case "deduct": 361 | break; 362 | case "dot": 363 | setIsSetLabelOpen(true); 364 | break; 365 | case "undo": 366 | const tempArray = [...prevState]; 367 | if(tempArray[tempArray.length - 1].type !== "deduct") { 368 | if(tempArray[tempArray.length - 1].type === "dot"){ 369 | const newArray = widgetData.map((item) => { 370 | if(item.type === "dot"){ 371 | console.log("dot shape is here"); 372 | const newItem = { 373 | area: item.area, 374 | subarea: item.subarea, 375 | category: item.category, 376 | subcategory: item.subcategory, 377 | type: "dot", 378 | unit:item.unit, 379 | measure:dotNum - 1, 380 | result: dotNum - 1, 381 | price: item.price 382 | } 383 | setDotNum(dotNum - 1); 384 | 385 | return newItem; 386 | } else return item; 387 | }) 388 | console.log("newArray = ", newArray); 389 | setWidgetData([...newArray]); 390 | } else { 391 | let tempWidget = [...widgetData]; 392 | tempWidget.pop(); 393 | setWidgetData(tempWidget); 394 | } 395 | 396 | } else { 397 | const lastState = tempArray[tempArray.length - 1]; 398 | widgetData.map((item, index) => { 399 | switch(item.type) { 400 | case "polygon": 401 | const DeductToDelete = [ 402 | { x: lastState.x, y: lastState.y }, 403 | { x: lastState.x + lastState.width, y: lastState.y }, 404 | { x: lastState.x + lastState.width, y: lastState.y + lastState.height }, 405 | { x: lastState.x, y: lastState.y + lastState.height} 406 | ]; 407 | if(isPolygonInsidePolygon(DeductToDelete, item.location)) { 408 | const prevDeductRects = [...item.deductRect]; 409 | prevDeductRects.pop(); 410 | 411 | const newWidget = [...widgetData]; 412 | let measured_value = item.measure; 413 | 414 | let calWidth = (Math.abs(lastState.width) * layoutSize / canvasWidth) * pageScale / 1000; 415 | let calHeight = (Math.abs(lastState.height) * layoutSize / canvasWidth) * pageScale / 1000; 416 | newWidget[index].deductRect = [...prevDeductRects]; 417 | newWidget[index].measure = (parseFloat(item.measure) + parseFloat((calWidth * calHeight).toFixed(2))).toFixed(2); 418 | let temp = item.price.map(element => { 419 | return (parseFloat((parseFloat(element) / parseFloat(measured_value)).toFixed(2)) * (parseFloat(parseFloat(measured_value) + (calWidth * calHeight)).toFixed(2))).toFixed(2) 420 | } ); 421 | newWidget[index].price = temp; 422 | 423 | setWidgetData(newWidget); 424 | } 425 | break; 426 | case "rect": 427 | const previousRect = { x: item.location[0].x, y: item.location[0].y, width: item.location[1].x - item.location[0].x, height: item.location[1].y - item.location[0].y} 428 | if(isUserRectangleInsidePrevious(lastState, previousRect)) { 429 | const prevDeductRects = [...item.deductRect]; 430 | prevDeductRects.pop(); 431 | 432 | const newWidget = [...widgetData]; 433 | let measured_value = item.measure; 434 | 435 | let calWidth = (Math.abs(lastState.width) * layoutSize / canvasWidth) * pageScale / 1000; 436 | let calHeight = (Math.abs(lastState.height) * layoutSize / canvasWidth) * pageScale / 1000; 437 | newWidget[index].deductRect = [...prevDeductRects]; 438 | newWidget[index].measure = (parseFloat(item.measure) + parseFloat((calWidth * calHeight).toFixed(2))).toFixed(2); 439 | 440 | let temp = item.price.map(element => { 441 | return (parseFloat((parseFloat(element) / parseFloat(measured_value)).toFixed(2)) * (parseFloat(parseFloat(measured_value) + parseFloat((calWidth * calHeight).toFixed(2))).toFixed(2))).toFixed(2); 442 | } ); 443 | newWidget[index].price = temp; 444 | 445 | setWidgetData(newWidget); 446 | } 447 | break; 448 | default: 449 | break; 450 | } 451 | }) 452 | } 453 | tempArray.pop(); 454 | setPrevState(tempArray); 455 | redraw() 456 | break; 457 | default: 458 | break; 459 | } 460 | setDrawType(type); 461 | } 462 | 463 | const deleteSeletedData = (id) => { 464 | console.log("selectedID = ", id); 465 | let newPrevState = prevState.filter(item => { 466 | if(item.id !== id) { 467 | return item; 468 | } 469 | }) 470 | let selectedPrevState = prevState.filter(item => { 471 | if(item.id === id) return item; 472 | }) 473 | if(selectedPrevState[0].type === "dot"){ 474 | const updatePrevState = prevState.filter((item) => { 475 | if(item.type !== "dot") return item; 476 | }) 477 | console.log("updatePrevState = ", updatePrevState); 478 | setPrevState([...updatePrevState]); 479 | const updateWidget = widgetData.filter(item => { 480 | if(!item.id || item.type !== "dot") return item; 481 | }) 482 | setWidgetData(updateWidget); 483 | } else { 484 | setPrevState(newPrevState); 485 | let newWidget = widgetData.filter(item => { 486 | if(!item.id || item.id !== id){ 487 | return item; 488 | } 489 | }) 490 | setWidgetData(newWidget); 491 | } 492 | 493 | } 494 | 495 | // ==================Drawing Annotation layer.======================== 496 | // ====Mouse Event Handler===== 497 | 498 | const handleMouseClick = (e) => { 499 | if (clickTimer === null) { 500 | setClickTimer(setTimeout(() => { 501 | handleSingleClick(e); 502 | setClickTimer(null); 503 | }, 300)); 504 | } else { 505 | clearTimeout(clickTimer); 506 | setClickTimer(null); 507 | handleDoubleClick(e); 508 | } 509 | }; 510 | 511 | const handleMouseWheel = (e) => { 512 | if(e.deltaY < 0) { 513 | setZoomScale(zoomScale + 0.2); 514 | } else { 515 | setZoomScale(zoomScale - 0.2); 516 | } 517 | } 518 | 519 | const handleSingleClick = (e) => { 520 | const drawcanvas = annotationLayerRef.current; 521 | const context = drawcanvas.getContext('2d'); 522 | redraw(); 523 | 524 | setStartPoint(getMouseClickPosition(drawcanvas, e)); 525 | setPreviousLegth(displayValue); 526 | setIsDrawing(true); 527 | if(drawType === "line") { 528 | setPolyLineTracker([ 529 | ...polyLineTracker, 530 | getMouseClickPosition(drawcanvas, e) 531 | ]); 532 | } 533 | if(drawType === "poly") { 534 | setPolyTracker([ 535 | ...polyTracker, 536 | getMouseClickPosition(drawcanvas, e) 537 | ]); 538 | } 539 | if(drawType === "dot") { 540 | setIsDrawing(false); 541 | redraw(); 542 | context.fillStyle = 'rgba(255, 0, 0, 0.7)'; 543 | 544 | context.arc(getMouseClickPosition(drawcanvas,e).x, getMouseClickPosition(drawcanvas, e).y, 15, 0, 2 * Math.PI); 545 | context.fill(); 546 | setDotNum(dotNum + 1); 547 | let id = uuidv4(); 548 | prevs.push({type: "dot", x: getMouseClickPosition(drawcanvas, e).x, y: getMouseClickPosition(drawcanvas, e).y}); 549 | setPrevState([ 550 | ...prevState, 551 | {id: id, type: "dot", x: getMouseClickPosition(drawcanvas, e).x, y: getMouseClickPosition(drawcanvas, e).y} 552 | ]) 553 | 554 | const dotArray = widgetData.filter(item => item.type === "dot"); 555 | console.log("dotArray = ", dotArray); 556 | if(dotArray.length) { 557 | console.log("dotArray is exist"); 558 | const newArray = widgetData.map((item) => { 559 | if(item.type === "dot"){ 560 | console.log("dot shape is here"); 561 | const newItem = { 562 | id: item.id, 563 | area: item.area, 564 | subarea: item.subarea, 565 | category: item.category, 566 | subcategory: item.subcategory, 567 | type: "dot", 568 | unit:item.unit, 569 | measure:dotNum + 1, 570 | result: dotNum + 1, 571 | price: item.price 572 | } 573 | 574 | return newItem; 575 | } else return item; 576 | }) 577 | console.log("newArray = ", newArray); 578 | setWidgetData([...newArray]); 579 | } else { 580 | console.log("dotArray isn't exist"); 581 | setWidgetData([ 582 | ...widgetData, 583 | { 584 | id: id, 585 | area: labelheader, 586 | subarea: labelsubheader, 587 | category: labelcategory, 588 | subcategory: labelsubcategory, 589 | type: "dot", 590 | unit: props.data.filter(item => item.name === labelcategory)[0].unit, 591 | measure: dotNum+1, 592 | result: dotNum+1, 593 | price: parseFloat(props.data.filter(item => item.name === labelcategory)[0].price) * (dotNum+1), 594 | } 595 | ]) 596 | } 597 | } 598 | }; 599 | 600 | function isUserRectangleInsidePrevious(userRect, previousRect) { 601 | const userTopLeft = { x: userRect.x, y: userRect.y }; 602 | const userTopRight = { x: userRect.x + userRect.width, y: userRect.y }; 603 | const userBottomLeft = { x: userRect.x, y: userRect.y + userRect.height }; 604 | const userBottomRight = { x: userRect.x + userRect.width, y: userRect.y + userRect.height }; 605 | 606 | return ( 607 | isPointInsideRectangle(userTopLeft, previousRect) && 608 | isPointInsideRectangle(userTopRight, previousRect) && 609 | isPointInsideRectangle(userBottomLeft, previousRect) && 610 | isPointInsideRectangle(userBottomRight, previousRect) 611 | ); 612 | } 613 | 614 | function isPointInsideRectangle(point, rect) { 615 | return ( 616 | point.x >= rect.x && 617 | point.x <= rect.x + rect.width && 618 | point.y >= rect.y && 619 | point.y <= rect.y + rect.height 620 | ); 621 | } 622 | 623 | function isPolygonInsidePolygon(polygon, previousPolygon) { 624 | // Convert the polygon vertices to vectors for easier calculations 625 | const polygonVectors = polygon.map(vertex => ({ x: vertex.x, y: vertex.y })); 626 | const previousPolygonVectors = previousPolygon.map(vertex => ({ x: vertex.x, y: vertex.y })); 627 | 628 | // Check if all vertices of the polygon are inside the previous polygon 629 | for (const vertex of polygonVectors) { 630 | if (!isPointInsidePolygon(vertex, previousPolygonVectors)) { 631 | return false; 632 | } 633 | } 634 | 635 | return true; 636 | } 637 | 638 | function isPointInsidePolygon(point, polygon) { 639 | // Ray casting algorithm to check if a point is inside a polygon 640 | let isInside = false; 641 | 642 | for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { 643 | const xi = polygon[i].x, yi = polygon[i].y; 644 | const xj = polygon[j].x, yj = polygon[j].y; 645 | 646 | const intersect = ((yi > point.y) !== (yj > point.y)) 647 | && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); 648 | 649 | if (intersect) { 650 | isInside = !isInside; 651 | } 652 | } 653 | 654 | return isInside; 655 | } 656 | 657 | 658 | const handleDoubleClick = (e) => { 659 | setIsDrawing(false); 660 | setIsDisplayValue(false); 661 | 662 | const canvas = annotationLayerRef.current; 663 | const context = canvas.getContext("2d"); 664 | 665 | context.strokeStyle = 'red'; 666 | context.lineWidth = "4"; 667 | context.fillStyle = "rgba(255, 0, 0, 0.7)"; 668 | let id = uuidv4(); 669 | let {x, y} = getMouseClickPosition(canvas, e); 670 | 671 | switch(drawType) { 672 | case "line": 673 | redraw(); 674 | 675 | context.beginPath(); 676 | context.moveTo(polyLineTracker[0].x, polyLineTracker[0].y); 677 | for(var i = 1; i < polyLineTracker.length; i++) { 678 | context.lineTo(polyLineTracker[i].x, polyLineTracker[i].y); 679 | context.stroke(); 680 | } 681 | context.lineTo(x, y); 682 | context.stroke(); 683 | 684 | prevs.push({type: "line", polyline: [...polyLineTracker, {x, y}]}); 685 | setPrevState([ 686 | ...prevState, 687 | {id: id, type: "line", polyline: [...polyLineTracker, {x, y}]} 688 | ]) 689 | 690 | let Linedata = props.data.filter(item => item.name === labelcategory)[0].subcategory; 691 | Linedata = JSON.parse(Linedata); 692 | 693 | let lineprice = []; 694 | 695 | for(var i = 0; i < labelsubcategory.length; i++) { 696 | let maindata = Linedata.filter(value => value.item === labelsubcategory[i])[0]; 697 | lineprice.push((maindata.price * (1 + maindata.wastage / 100) * displayValue).toFixed(2)) 698 | } 699 | 700 | setWidgetData([ 701 | ...widgetData, 702 | { 703 | id:id, 704 | area: labelheader, 705 | subarea: labelsubheader, 706 | category: labelcategory, 707 | subcategory: labelsubcategory, 708 | type: "polyline", 709 | unit: props.data.filter(item => item.name === labelcategory)[0].unit, 710 | measure: displayValue.toFixed(2), 711 | result:displayValue.toFixed(2), 712 | price: lineprice, 713 | location: [...polyLineTracker, {x, y}], 714 | deductRect: [], 715 | } 716 | ]) 717 | setDisplayValue(0); 718 | setStartPoint({}); 719 | setPolyLineTracker([]); 720 | context.closePath(); 721 | break; 722 | case "rect": 723 | id = uuidv4(); 724 | context.rect(startPoint.x, startPoint.y, x - startPoint.x, y - startPoint.y); 725 | context.fill(); 726 | 727 | prevs.push({type: "rect", x: startPoint.x, y: startPoint.y, width: x - startPoint.x, height: y - startPoint.y}); 728 | setPrevState([ 729 | ...prevState, 730 | {id: id, type: "rect", x: startPoint.x, y: startPoint.y, height: y - startPoint.y, width: x - startPoint.x} 731 | ]) 732 | 733 | let RectData = props.data.filter(item => item.name === labelcategory)[0].subcategory; 734 | RectData = JSON.parse(RectData); 735 | 736 | let Rectprice = []; 737 | 738 | for(var i = 0; i < labelsubcategory.length; i++) { 739 | let maindata = RectData.filter(value => value.item === labelsubcategory[i])[0]; 740 | Rectprice.push((maindata.price * (1 + maindata.wastage / 100) * displayValue).toFixed(2)) 741 | } 742 | 743 | setWidgetData([ 744 | ...widgetData, 745 | { 746 | id: id, 747 | area: labelheader, 748 | subarea: labelsubheader, 749 | category: labelcategory, 750 | subcategory: labelsubcategory, 751 | type: "rect", 752 | unit: props.data.filter(item => item.name === labelcategory)[0].unit, 753 | measure: displayValue, 754 | result:displayValue, 755 | price: Rectprice, 756 | location: [startPoint, {x, y}], 757 | deductRect: [] 758 | } 759 | ]) 760 | setStartPoint({}); 761 | setDisplayValue(0); 762 | redraw(); 763 | break; 764 | case "poly": 765 | 766 | context.beginPath(); 767 | context.moveTo(polyTracker[0].x, polyTracker[0].y); 768 | for(var i = 1; i < polyTracker.length; i++) { 769 | context.lineTo(polyTracker[i].x, polyTracker[i].y); 770 | context.stroke(); 771 | } 772 | context.lineTo(x, y); 773 | context.stroke(); 774 | context.fill(); 775 | 776 | polyTracker.push({x:x, y:y}); 777 | 778 | prevs.push({type: "poly", track: polyTracker}); 779 | 780 | let area = 0; 781 | 782 | for(i = 0; i < polyTracker.length; i++) { 783 | const {x: x1, y: y1} = polyTracker[i]; 784 | const {x: x2, y: y2} = polyTracker[(i + 1) % polyTracker.length]; 785 | 786 | area += (x1 * layoutSize / canvasWidth * pageScale / 1000) * (y2 * layoutSize / canvasWidth * pageScale / 1000) - (x2 * layoutSize / canvasWidth * pageScale / 1000) * (y1 * layoutSize / canvasWidth * pageScale / 1000); 787 | } 788 | 789 | area = Math.abs(area / 2).toFixed(2); 790 | 791 | let PolyData = props.data.filter(item => item.name === labelcategory)[0].subcategory; 792 | PolyData = JSON.parse(PolyData); 793 | 794 | let Polyprice = []; 795 | 796 | for(var i = 0; i < labelsubcategory.length; i++) { 797 | let maindata = PolyData.filter(value => value.item === labelsubcategory[i])[0]; 798 | Polyprice.push((maindata.price * (1 + maindata.wastage / 100) * displayValue).toFixed(2)) 799 | } 800 | 801 | setPrevState([ 802 | ...prevState, 803 | {id : id, type: "poly", track: polyTracker} 804 | ]) 805 | 806 | setWidgetData([ 807 | ...widgetData, 808 | { 809 | id: id, 810 | area: labelheader, 811 | subarea: labelsubheader, 812 | category: labelcategory, 813 | subcategory: labelsubcategory, 814 | type: "polygon", 815 | unit: props.data.filter(item => item.name === labelcategory)[0].unit, 816 | measure: displayValue, 817 | result:displayValue, 818 | price: Polyprice, 819 | location: polyTracker, 820 | deductRect:[], 821 | } 822 | ]); 823 | 824 | setStartPoint({}); 825 | setPolyTracker([]); 826 | setDisplayValue(0); 827 | context.closePath(); 828 | break; 829 | case "deduct": 830 | widgetData.map((item, index) => { 831 | switch(item.type) { 832 | case "polygon": 833 | // Convert the user-drawn rectangle to polygon vertices 834 | const userPolygonVertices = [ 835 | { x: startPoint.x, y: startPoint.y }, 836 | { x: x, y: startPoint.y }, 837 | { x: x, y: y }, 838 | { x: startPoint.x, y: y } 839 | ]; 840 | 841 | // Check if the user-drawn rectangle is completely inside the previous polygon 842 | let isInside_Poly = isPolygonInsidePolygon(userPolygonVertices, item.location); 843 | 844 | if (isInside_Poly) { 845 | 846 | if(item.deductRect.length === 0){ 847 | context.clearRect(startPoint.x, startPoint.y, x - startPoint.x, y - startPoint.y); 848 | } 849 | else { 850 | item.deductRect.forEach(element => context.clearRect(element.x, element.y, element.x1 - element.x, element.y1 - element.y)); 851 | } 852 | 853 | setPrevState([ 854 | ...prevState, 855 | {id: item.id, type: "deduct", x: startPoint.x, y: startPoint.y, height: y - startPoint.y, width: x - startPoint.x} 856 | ]) 857 | 858 | const newArray = [...widgetData]; 859 | let measured_value = item.measure; 860 | newArray[index].deductRect = [...item.deductRect, {x: startPoint.x, y:startPoint.y, x1: x, y1: y}]; 861 | newArray[index].measure = (item.measure - displayValue).toFixed(2); 862 | 863 | let temp = item.price.map(element => { 864 | return ((parseFloat(element) / measured_value) * (measured_value - displayValue)).toFixed(2); 865 | }); 866 | 867 | newArray[index].price = temp; 868 | 869 | setWidgetData(newArray); 870 | 871 | } 872 | break; 873 | 874 | case "rect": 875 | 876 | const previousRect = { x: item.location[0].x, y: item.location[0].y, width: item.location[1].x - item.location[0].x, height: item.location[1].y - item.location[0].y} 877 | 878 | const deductRect = { x: startPoint.x, y: startPoint.y, width: x - startPoint.x, height: y - startPoint.y }; 879 | 880 | const isInside = isUserRectangleInsidePrevious(deductRect, previousRect); 881 | 882 | if(isInside){ 883 | 884 | context.clearRect(startPoint.x, startPoint.y, x - startPoint.x, y - startPoint.y); 885 | 886 | setPrevState([ 887 | ...prevState, 888 | {id: item.id, type: "deduct", x: startPoint.x, y: startPoint.y, height: y - startPoint.y, width: x - startPoint.x} 889 | ]) 890 | 891 | const newArray = [...widgetData]; 892 | 893 | newArray[index].deductRect = [...item.deductRect, {x: startPoint.x, y:startPoint.y, x1: x, y1: y}]; 894 | newArray[index].measure = parseFloat((item.measure - displayValue).toFixed(2)); 895 | 896 | let temp = item.price.map(element => parseFloat(((parseFloat(element) / item.measure) * (item.measure - displayValue)).toFixed(2))); 897 | 898 | newArray[index].price = temp; 899 | 900 | setWidgetData(newArray); 901 | 902 | } 903 | break; 904 | default: 905 | break; 906 | } 907 | }) 908 | break; 909 | } 910 | 911 | }; 912 | 913 | const handleMouseMove = (e) => { 914 | if(isDrawing) { 915 | redraw(); 916 | const drawcanvas = annotationLayerRef.current; 917 | var context = drawcanvas.getContext("2d"); 918 | 919 | let {x, y} = getMouseClickPosition(drawcanvas, e); 920 | let diffX, diffY = 0; 921 | 922 | setMousePointX(e.clientX); 923 | setMousePointY(e.clientY); 924 | 925 | context.strokeStyle = 'red'; 926 | context.lineWidth = "4"; 927 | context.fillStyle = "rgba(255, 0, 0, 0.7)"; 928 | 929 | context.beginPath(); 930 | 931 | switch(drawType) { 932 | case "line": 933 | diffX = (Math.abs(startPoint.x - x) * layoutSize / canvasWidth) * pageScale; 934 | diffY = (Math.abs(startPoint.y - y) * layoutSize / canvasWidth) * pageScale; 935 | setDisplayValue(previouslength + (Math.ceil(Math.sqrt(diffX * diffX + diffY * diffY)) / 1000)); 936 | 937 | setIsDisplayValue(true); 938 | 939 | context.moveTo(polyLineTracker[0].x, polyLineTracker[0].y); 940 | 941 | for(var i = 1; i < polyLineTracker.length; i++) { 942 | context.lineTo(polyLineTracker[i].x, polyLineTracker[i].y); 943 | context.stroke(); 944 | } 945 | context.lineTo(x, y); 946 | context.stroke(); 947 | break; 948 | case "rect": 949 | let drawrectwidth = (x - startPoint.x); 950 | let drawrectheight = (y - startPoint.y); 951 | diffX = (Math.abs(startPoint.x - x) * layoutSize / canvasWidth) * pageScale; 952 | diffY = (Math.abs(startPoint.y - y) * layoutSize / canvasWidth) * pageScale; 953 | setDisplayValue((diffX / 1000 * diffY / 1000).toFixed(2)); 954 | 955 | setIsDisplayValue(true); 956 | context.rect(startPoint.x, startPoint.y, drawrectwidth, drawrectheight); 957 | context.stroke(); 958 | break; 959 | case "poly": 960 | 961 | context.moveTo(polyTracker[0].x, polyTracker[0].y); 962 | for(var i = 1; i < polyTracker.length; i++) { 963 | context.lineTo(polyTracker[i].x, polyTracker[i].y); 964 | context.stroke(); 965 | } 966 | context.lineTo(x, y); 967 | context.stroke(); 968 | diffX = (Math.abs(startPoint.x - x) * layoutSize / canvasWidth) * pageScale; 969 | diffY = (Math.abs(startPoint.y - y) * layoutSize / canvasWidth) * pageScale; 970 | 971 | polyTracker.push({x:x, y:y}); 972 | 973 | let area = 0; 974 | 975 | for(i = 0; i < polyTracker.length; i++) { 976 | const {x: x1, y: y1} = polyTracker[i]; 977 | const {x: x2, y: y2} = polyTracker[(i + 1) % polyTracker.length]; 978 | 979 | area += (x1 * layoutSize / canvasWidth * pageScale / 1000) * (y2 * layoutSize / canvasWidth * pageScale / 1000) - (x2 * layoutSize / canvasWidth * pageScale / 1000) * (y1 * layoutSize / canvasWidth * pageScale / 1000); 980 | } 981 | 982 | area = Math.abs(area / 2).toFixed(2); 983 | polyTracker.pop(); 984 | 985 | setIsDisplayValue(true); 986 | setDisplayValue(area); 987 | break; 988 | case "deduct": 989 | redraw(); 990 | let deductX = (x - startPoint.x); 991 | let deductY = (y - startPoint.y); 992 | diffX = (Math.abs(startPoint.x - x) * layoutSize / canvasWidth) * pageScale; 993 | diffY = (Math.abs(startPoint.y - y) * layoutSize / canvasWidth) * pageScale; 994 | setDisplayValue((diffX / 1000 * diffY / 1000).toFixed(2)); 995 | 996 | setIsDisplayValue(true); 997 | context.clearRect(startPoint.x, startPoint.y, deductX, deductY); 998 | break; 999 | default: 1000 | break; 1001 | } 1002 | } 1003 | } 1004 | 1005 | /* ==== Get Mouse Click Position ===== */ 1006 | 1007 | const getMouseClickPosition = (canvas, e) => { 1008 | var rect = canvas.getBoundingClientRect(); 1009 | return { 1010 | x: (e.clientX - rect.left) * canvas.width / rect.width, 1011 | y: (e.clientY - rect.top) * canvas.height / rect.height, 1012 | } 1013 | } 1014 | 1015 | const DataSelect = (ids) => { 1016 | setSelectedID(ids); 1017 | redraw(); 1018 | } 1019 | 1020 | const redraw = () => { 1021 | const canvas = annotationLayerRef.current; 1022 | const context = canvas.getContext("2d"); 1023 | context.clearRect(0, 0, canvas.width, canvas.height); 1024 | 1025 | 1026 | prevState && prevState.map(item => { 1027 | context.beginPath(); 1028 | if(item.type === "line") { 1029 | var polydots = item.polyline; 1030 | context.moveTo(polydots[0].x, polydots[0].y); 1031 | for(var i = 1; i < polydots.length; i++) { 1032 | context.lineTo(polydots[i].x, polydots[i].y); 1033 | } 1034 | 1035 | context.strokeStyle = 'red'; 1036 | 1037 | selectedID.forEach(element => { 1038 | if(element === item.id){ 1039 | context.strokeStyle = "blue" 1040 | } 1041 | }) 1042 | 1043 | context.lineWidth = "4"; 1044 | context.stroke(); 1045 | 1046 | } else if (item.type === "rect") { 1047 | context.rect(item.x, item.y, item.width, item.height); 1048 | context.fillStyle = "rgba(255, 0, 0, 0.7)"; 1049 | selectedID && selectedID.forEach(element => { 1050 | if(element === item.id) context.fillStyle = "rgba(0, 0, 255, 0.7)"; 1051 | }); 1052 | 1053 | context.fill(); 1054 | } else if(item.type === "poly") { 1055 | var points = item.track; 1056 | context.moveTo(points[0].x, points[0].y); 1057 | for(var i = 1; i < points.length ; i++) { 1058 | context.lineTo(points[i].x, points[i].y); 1059 | } 1060 | 1061 | context.lineTo(points[0].x, points[0].y); 1062 | context.fillStyle = "rgba(255, 0, 0, 0.7)"; 1063 | selectedID.forEach(element => { 1064 | if(element === item.id) context.fillStyle = "rgba(0, 0, 255, 0.7)"; 1065 | }); 1066 | context.fill(); 1067 | } else if(item.type === "dot") { 1068 | context.fillStyle = 'rgba(255, 0, 0, 0.7)'; 1069 | context.arc(item.x, item.y, 15, 0, 2 * Math.PI); 1070 | context.fill(); 1071 | } 1072 | else if(item.type === "deduct") { 1073 | context.clearRect(item.x, item.y, item.width, item.height); 1074 | } 1075 | }) 1076 | 1077 | 1078 | } 1079 | 1080 | return ( 1081 |
1082 | { 1083 | !file ? 1084 |
1087 | 1099 |
1100 | : "" 1101 | } 1102 | 1103 | { 1104 | file && ( 1105 |
1106 | } 1108 | type="primary" 1109 | style={{ 1110 | right: "45%", 1111 | }} 1112 | onClick={() => (setZoomScale(zoomScale + 0.2))} 1113 | tooltip="Zoom In" 1114 | /> 1115 | } 1117 | type="primary" 1118 | style={{ 1119 | right: "55%", 1120 | }} 1121 | onClick={() => (setZoomScale(zoomScale - 0.2))} 1122 | tooltip="Zoom Out" 1123 | /> 1124 | } 1126 | type="primary" 1127 | style={{ 1128 | right: "10%", 1129 | }} 1130 | onClick={() => { 1131 | props.logout(); 1132 | navigate('/signin', {replace:true}) 1133 | }} 1134 | tooltip="Log out" 1135 | /> 1136 | 1137 | 1149 | 1150 |
1151 |
1152 |
1153 | 1158 |
1159 | {Array.from(Array(numPages), (e, i) => ( 1160 |
{setPageNumber(i + 1)}}> 1161 | 1162 |

{i + 1}

1163 |
1164 | ))} 1165 |
1166 | setIsChangeScaleModalOpen(true)} pageScale ={pageScale} pageSize = {pageSize}/> 1167 |
1168 |
1169 |
1170 |
1171 | 1172 | 1173 | 1174 | { 1175 | isDisplayValue ?

1181 | {displayValue} 1182 |

: "" 1183 | } 1184 | 1185 |
1186 | 1191 | 1199 |
1200 |
1201 |
1202 |
1203 |
1204 |
1205 | ) 1206 | } 1207 | 1208 | 1209 | {/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */} 1210 | {/* Modal for Set Scale Page */} 1211 | 1217 |
1218 | Please Input the size and scale of paper 1219 |
1220 |
1221 | 1222 | 1227 |
1228 |
1229 |
1230 | 1231 | 1240 |
1241 |
1242 | 1243 | {/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */} 1244 | { /* Modal For Change Scale */} 1245 | 1251 |
1252 | Please Input the size and scale of paper 1253 |
1254 |
1255 | 1256 | 1261 |
1262 |
1263 | 1264 | {/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */} 1265 | {/* Modal for Set Label */} 1266 | 1272 |
1273 | 1274 | 1282 |
1283 | 1284 |
1285 | 1286 | 1294 |
1295 | 1296 |
1297 | 1298 | 1306 |
1307 | 1308 |
1309 | 1310 | 1323 |
1324 |
1325 |
1326 | ); 1327 | }; 1328 | 1329 | const mapStateToProps = (state) => ({ 1330 | data: state.admin.data 1331 | }) 1332 | 1333 | export default connect(mapStateToProps,{retrieveData, logout})(App); -------------------------------------------------------------------------------- /frontend/src/containers/Construction/Scale/Scale.js: -------------------------------------------------------------------------------- 1 | const Scale = (props) => { 2 | 3 | return ( 4 |
5 |
6 |
PAGE SETTINGS
7 |
8 |
9 |

Page Size

10 | 15 |
16 |
17 |

Page scale

18 | 24 |
25 | 26 |
27 | ) 28 | } 29 | 30 | export default Scale; -------------------------------------------------------------------------------- /frontend/src/containers/index.jsx: -------------------------------------------------------------------------------- 1 | import Signin from "./signin/Signin"; 2 | import Construction from './Construction/Main/Main'; 3 | import Admin from './Admin/Admin'; 4 | 5 | 6 | export { 7 | Signin, 8 | Construction, 9 | Admin, 10 | } -------------------------------------------------------------------------------- /frontend/src/containers/signin/Signin.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react' 2 | import './Signin.scss' 3 | import { Input, Button, Form, notification } from 'antd'; 4 | import { useNavigate } from "react-router-dom"; 5 | import { connect } from 'react-redux'; 6 | import { login } from '../../actions/auth'; 7 | 8 | const Signin = (props) => { 9 | 10 | const [api, contextHolder] = notification.useNotification(); 11 | 12 | const openNotificationWithIcon = (type, error, title) => { 13 | api[type]({ 14 | message: title, 15 | description: error.msg, 16 | }); 17 | }; 18 | 19 | useEffect(() => { 20 | props.errors.map((item, index) => openNotificationWithIcon('error',props.errors[index], "Errors")) 21 | }, [props.errors]) 22 | 23 | useEffect(() => { 24 | if(props.isAuthenticated === true){ 25 | if(!props.role) 26 | navigate('/main', {replace: true}) 27 | else navigate('/admin', {replace: true}) 28 | } 29 | }, [props.isAuthenticated]) 30 | 31 | const navigate = useNavigate(); 32 | 33 | const [formData, setFormData] = useState({ 34 | email: '', 35 | password: '' 36 | }); 37 | 38 | const { email, password } = formData; 39 | 40 | const onChange = (e) => { 41 | setFormData({ ...formData, [e.target.name]: e.target.value }); 42 | } 43 | 44 | const onFinish = (e) => { 45 | props.login(email, password); 46 | }; 47 | 48 | return( 49 |
50 | {contextHolder} 51 |
52 |

Login

53 |
59 | 68 | 76 | 77 | 86 | 94 | 95 | 101 | 102 |
103 |
104 | ) 105 | } 106 | 107 | 108 | const mapStateToProps = (state) => ({ 109 | isAuthenticated: state.auth.isAuthenticated, 110 | errors: state.auth.errors, 111 | role: state.auth.role 112 | }) 113 | 114 | export default connect(mapStateToProps, {login} )(Signin) -------------------------------------------------------------------------------- /frontend/src/containers/signin/Signin.scss: -------------------------------------------------------------------------------- 1 | .login-container 2 | { 3 | .box{ 4 | position:absolute; 5 | top:50%; 6 | left:50%; 7 | transform:translate(-50%,-50%); 8 | width:30%; 9 | padding:40px; 10 | background:rgba(0,0,0,.8); 11 | box-sizing:border-box; 12 | box-shadow:0 15px 25px rgba(0,0,0,.5); 13 | border-radius:10px; 14 | h2{ 15 | margin:0 0 30px; 16 | padding:0; 17 | color:#fff; 18 | text-align:center; 19 | } 20 | label{ 21 | color:white; 22 | margin-bottom: 20px; 23 | } 24 | .inputbox{ 25 | margin-bottom: 20px; 26 | } 27 | .signup-link{ 28 | color: white; 29 | span{ 30 | color: #03a9f4; 31 | margin-left: 10px; 32 | cursor: pointer; 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | margin:0px; 4 | } -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | import './index.css' 6 | 7 | import "bootstrap/dist/css/bootstrap.min.css"; 8 | import "bootstrap/dist/js/bootstrap.bundle.min"; 9 | 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | // If you want to start measuring performance in your app, pass a function 19 | // to log results (for example: reportWebVitals(console.log)) 20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 21 | reportWebVitals(); 22 | -------------------------------------------------------------------------------- /frontend/src/reducers/admin.js: -------------------------------------------------------------------------------- 1 | import { 2 | RETRIEVE_DATA 3 | } from '../actions/types'; 4 | 5 | 6 | const initialState = { 7 | data: null 8 | }; 9 | 10 | function adminReducer (state = initialState, action){ 11 | const {type, payload} = action; 12 | 13 | switch(type){ 14 | 15 | case RETRIEVE_DATA: 16 | return { 17 | ...state, 18 | data: payload 19 | } 20 | default: 21 | return state; 22 | break; 23 | } 24 | 25 | } 26 | 27 | export default adminReducer -------------------------------------------------------------------------------- /frontend/src/reducers/alert.js: -------------------------------------------------------------------------------- 1 | import { SET_ALERT, REMOVE_ALERT } from '../actions/types'; 2 | 3 | const initialState = []; 4 | 5 | function alertReducer(state = initialState, action) { 6 | const { type, payload } = action; 7 | 8 | switch (type) { 9 | case SET_ALERT: 10 | return [...state, payload]; 11 | case REMOVE_ALERT: 12 | return state.filter((alert) => alert.id !== payload); 13 | default: 14 | return state; 15 | } 16 | } 17 | 18 | export default alertReducer; -------------------------------------------------------------------------------- /frontend/src/reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_LOADED, 3 | AUTH_ERROR, 4 | LOGIN_SUCCESS, 5 | LOGOUT, 6 | LOGIN_FAIL, 7 | } from '../actions/types'; 8 | 9 | const initialState = { 10 | token: localStorage.getItem('token'), 11 | isAuthenticated: null, 12 | loading: true, 13 | user: null, 14 | role : false, 15 | errors: [] 16 | }; 17 | 18 | function authReducer(state = initialState, action) { 19 | const { type, payload } = action; 20 | 21 | switch (type) { 22 | case USER_LOADED: 23 | return { 24 | ...state, 25 | isAuthenticated: true, 26 | loading: false, 27 | user: payload 28 | }; 29 | case LOGIN_FAIL: 30 | return { 31 | ...state, 32 | errors: [...state.errors, ...payload] 33 | } 34 | case LOGIN_SUCCESS: 35 | const {role} = payload; 36 | return { 37 | ...state, 38 | role, 39 | isAuthenticated: true, 40 | loading: false, 41 | user: payload 42 | }; 43 | case AUTH_ERROR: 44 | case LOGOUT: 45 | return { 46 | ...state, 47 | token: null, 48 | isAuthenticated: false, 49 | loading: false, 50 | user: null 51 | }; 52 | default: 53 | return state; 54 | } 55 | } 56 | 57 | export default authReducer; -------------------------------------------------------------------------------- /frontend/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import alert from './alert'; 3 | import auth from './auth'; 4 | import admin from './admin'; 5 | import result from './result'; 6 | 7 | export default combineReducers({ 8 | auth, 9 | alert, 10 | admin, 11 | result, 12 | }); -------------------------------------------------------------------------------- /frontend/src/reducers/result.js: -------------------------------------------------------------------------------- 1 | import { 2 | SAVE_RESULT 3 | } from '../actions/types'; 4 | 5 | const initilaState = { 6 | data : null 7 | }; 8 | 9 | function resultReducer (state = initilaState, action) { 10 | const {type, payload} = action; 11 | 12 | switch(type) { 13 | case SAVE_RESULT: 14 | return { 15 | ...state, 16 | data: payload 17 | } 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | export default resultReducer -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/routes/MyRoutes.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 2 | import { Signin, Construction, Admin} from "../containers"; 3 | 4 | const MyRoutes = () => { 5 | return ( 6 | 7 | 8 | } /> 9 | } /> 10 | } /> 11 | } /> 12 | 13 | 14 | ) 15 | } 16 | 17 | export default MyRoutes -------------------------------------------------------------------------------- /frontend/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const { createProxyMiddleware } = require('http-proxy-middleware'); 2 | 3 | module.exports = function(app) { 4 | app.use( 5 | '/api', 6 | createProxyMiddleware({ 7 | target: 'http://localhost:5000', 8 | changeOrigin: true, 9 | }) 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from './reducers'; 5 | import setAuthToken from './utils/setAuthToken'; 6 | import logger from 'redux-logger' 7 | 8 | const initialState = {}; 9 | 10 | const middleware = [thunk]; 11 | 12 | const store = createStore( 13 | rootReducer, 14 | initialState, 15 | composeWithDevTools(applyMiddleware(...middleware, logger)) 16 | ); 17 | 18 | /* 19 | NOTE: set up a store subscription listener 20 | to store the users token in localStorage 21 | */ 22 | 23 | /* 24 | initialize current state from redux store for subscription comparison 25 | preventing undefined error 26 | */ 27 | let currentState = store.getState(); 28 | 29 | store.subscribe(() => { 30 | // keep track of the previous and current state to compare changes 31 | let previousState = currentState; 32 | currentState = store.getState(); 33 | // if the token changes set the value in localStorage and axios headers 34 | if (previousState.auth.token !== currentState.auth.token) { 35 | const token = currentState.auth.token; 36 | setAuthToken(token); 37 | } 38 | }); 39 | 40 | export default store; -------------------------------------------------------------------------------- /frontend/src/utils/Spinner.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | // import spinner from './spinner.gif'; 3 | 4 | const Spinner = () => ( 5 | 6 |

Loading...

13 |
14 | ); 15 | 16 | export default Spinner; -------------------------------------------------------------------------------- /frontend/src/utils/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import store from '../store'; 3 | import { LOGOUT } from '../actions/types'; 4 | 5 | // Create an instance of axios 6 | const api = axios.create({ 7 | baseURL: '/api', 8 | headers: { 9 | 'Content-Type': 'application/json' 10 | } 11 | }); 12 | /* 13 | NOTE: intercept any error responses from the api 14 | and check if the token is no longer valid. 15 | ie. Token has expired or user is no longer 16 | authenticated. 17 | logout the user if the token has expired 18 | */ 19 | 20 | api.interceptors.response.use( 21 | (res) => res, 22 | (err) => { 23 | if (err.response.status === 401) { 24 | store.dispatch({ type: LOGOUT }); 25 | } 26 | return Promise.reject(err); 27 | } 28 | ); 29 | 30 | export default api; -------------------------------------------------------------------------------- /frontend/src/utils/setAuthToken.js: -------------------------------------------------------------------------------- 1 | import api from './api'; 2 | 3 | // store our JWT in LS and set axios headers if we do have a token 4 | 5 | const setAuthToken = (token) => { 6 | if (token) { 7 | api.defaults.headers.common['x-auth-token'] = token; 8 | localStorage.setItem('token', token); 9 | } else { 10 | delete api.defaults.headers.common['x-auth-token']; 11 | localStorage.removeItem('token'); 12 | } 13 | }; 14 | 15 | export default setAuthToken; --------------------------------------------------------------------------------