├── 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 |
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 |
13 |
--------------------------------------------------------------------------------
/frontend/build/icon-deduct.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/frontend/build/icon-poly.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/frontend/build/icon-rect.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/build/icon-undo.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
13 |
--------------------------------------------------------------------------------
/frontend/public/icon-deduct.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/frontend/public/icon-poly.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/frontend/public/icon-rect.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/public/icon-search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/public/icon-undo.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
469 |
470 |
476 |
477 |
Edit SubCategory
478 |
493 |
494 |
495 |
496 |
503 |
504 | Category Items
505 |
511 |
512 |
513 | Type Of measure
514 |
520 |
521 |
522 |
SubCategory
523 |
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 |

17 |
18 |
handleDrawType("rect")}
22 | >
23 |

24 |
Rectangle
25 |
26 |
handleDrawType("poly")}>
27 |

28 |
Polygon
29 |
30 |
31 |
handleDrawType("line")}>
32 |

33 |
Length
34 |
35 |
handleDrawType("deduct")}>
36 |

37 |
Deduct
38 |
39 |
handleDrawType("dot")}>
40 |

41 |
Count
42 |
43 |
handleDrawType("undo")}>
44 |

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 |
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 |
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 |
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 |
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;
--------------------------------------------------------------------------------