diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..729f927 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "printWidth": 120 +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 349d671..02732ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,14 +11,24 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "classnames": "^2.3.1", + "gh-pages": "^4.0.0", "moment": "^2.29.4", "randomcolor": "^0.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.5", + "react-redux": "^8.0.2", "react-scripts": "5.0.1", + "react-tooltip": "^4.2.21", + "redux": "^4.2.0", + "redux-thunk": "^2.4.1", + "thunk": "^0.0.1", "uuid": "^8.3.2", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "prettier": "2.7.1" } }, "node_modules/@ampproject/remapping": { @@ -3675,6 +3685,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3985,6 +4004,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -4656,6 +4680,14 @@ "node": ">=8" } }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", @@ -5443,6 +5475,11 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" }, + "node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "node_modules/clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -6551,6 +6588,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.185.tgz", "integrity": "sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw==" }, + "node_modules/email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -7779,6 +7821,30 @@ "node": ">=10" } }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -8231,6 +8297,95 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gh-pages": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", + "integrity": "sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ==", + "dependencies": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/gh-pages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/gh-pages/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/gh-pages/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8428,6 +8583,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -12224,6 +12392,25 @@ "node": ">=0.10.0" } }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -12232,6 +12419,11 @@ "node": ">= 6" } }, + "node_modules/pjs": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/pjs/-/pjs-5.1.2.tgz", + "integrity": "sha512-aoPrjtnYOknat/wiO5hyCkub7LymxSY1BN8gIsW49PfbdPyQiaU+UOo9DfSEhhl48cWLd28IwpERutBveLdQLA==" + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -13536,6 +13728,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -13947,6 +14154,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz", + "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -14027,6 +14277,30 @@ } } }, + "node_modules/react-tooltip": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.21.tgz", + "integrity": "sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==", + "dependencies": { + "prop-types": "^15.7.2", + "uuid": "^7.0.3" + }, + "engines": { + "npm": ">=6.13" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/react-tooltip/node_modules/uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -14093,6 +14367,22 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15101,6 +15391,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -15455,6 +15756,17 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" }, + "node_modules/thunk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/thunk/-/thunk-0.0.1.tgz", + "integrity": "sha512-pmOMtF63oJP7v+Hka9yRmcoPVeLGZtq2S0NwAh+rE8guWEOcg27dddYsJYZSJiKg4DTs/i71eaUDEvyCuTDNrg==", + "dependencies": { + "pjs": "*" + }, + "engines": { + "node": "*" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -15524,6 +15836,17 @@ "node": ">=8" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -15770,6 +16093,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -19240,6 +19571,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -19512,6 +19852,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -19998,6 +20343,11 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==" + }, "array.prototype.flat": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", @@ -20574,6 +20924,11 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" }, + "classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -21378,6 +21733,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.185.tgz", "integrity": "sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw==" }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -22284,6 +22644,21 @@ } } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==" + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -22588,6 +22963,78 @@ "get-intrinsic": "^1.1.1" } }, + "gh-pages": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", + "integrity": "sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ==", + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -22727,6 +23174,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25470,11 +25932,29 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "requires": { + "pinkie": "^2.0.0" + } + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" }, + "pjs": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/pjs/-/pjs-5.1.2.tgz", + "integrity": "sha512-aoPrjtnYOknat/wiO5hyCkub7LymxSY1BN8gIsW49PfbdPyQiaU+UOo9DfSEhhl48cWLd28IwpERutBveLdQLA==" + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -26229,6 +26709,12 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -26535,6 +27021,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz", + "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -26595,6 +27101,22 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-tooltip": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.21.tgz", + "integrity": "sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==", + "requires": { + "prop-types": "^15.7.2", + "uuid": "^7.0.3" + }, + "dependencies": { + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + } + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -26648,6 +27170,20 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -27395,6 +27931,14 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -27651,6 +28195,14 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" }, + "thunk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/thunk/-/thunk-0.0.1.tgz", + "integrity": "sha512-pmOMtF63oJP7v+Hka9yRmcoPVeLGZtq2S0NwAh+rE8guWEOcg27dddYsJYZSJiKg4DTs/i71eaUDEvyCuTDNrg==", + "requires": { + "pjs": "*" + } + }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -27704,6 +28256,14 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -27876,6 +28436,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 140b8d8..93cf1a7 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,24 @@ "name": "codemu", "version": "0.1.0", "private": true, + "homepage": "https://andrsvsrg.github.io/reactToDo/", "dependencies": { "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "classnames": "^2.3.1", + "gh-pages": "^4.0.0", "moment": "^2.29.4", "randomcolor": "^0.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.5", + "react-redux": "^8.0.2", "react-scripts": "5.0.1", + "react-tooltip": "^4.2.21", + "redux": "^4.2.0", + "redux-thunk": "^2.4.1", + "thunk": "^0.0.1", "uuid": "^8.3.2", "web-vitals": "^2.1.4" }, @@ -19,7 +27,9 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "predeploy": "react-scripts build", + "deploy": "gh-pages -d build" }, "eslintConfig": { "extends": [ @@ -38,5 +48,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "prettier": "2.7.1" } } diff --git a/public/index.html b/public/index.html index a5b2e32..eba6c85 100644 --- a/public/index.html +++ b/public/index.html @@ -7,11 +7,11 @@ content="Web site created using create-react-app" /> React App - +
diff --git a/src/App.css b/src/App.css index 104fbe0..23be38d 100644 --- a/src/App.css +++ b/src/App.css @@ -1,22 +1,28 @@ -*, *::before, *::after { +*, +*::before, +*::after { box-sizing: border-box; } + .app { display: flex; - flex-direction:column; - font-family: 'Montserrat', sans-serif; + flex-direction: column; + font-family: "Montserrat", sans-serif; width: 100%; height: 100vh; margin: 0; - background-color: #20252B; + background-color: #20252b; color: white; } + .header .addtodo_div { flex-grow: 0; } -.draggable_fild { + +.draggable-fild { flex-grow: 10; } + .calendar { flex-grow: 0; -} \ No newline at end of file +} diff --git a/src/App.js b/src/App.js index b7b22f6..fd9e1e7 100644 --- a/src/App.js +++ b/src/App.js @@ -1,44 +1,30 @@ -import moment from 'moment' -import React, { useEffect, useState } from 'react' - -import Calendar from './components/Calendar/Calendar' -import AddToDo from './components/AddToDo/AddToDo' -import Header from './components/Header/Header' -import ListItem from './components/ListItem/ListItem' - -import './App.css' - -function App() { - - const [todo,setToDo] = useState( - JSON.parse(localStorage.getItem('items')) || {} - - ) - - useEffect(() => { - localStorage.setItem('items',JSON.stringify(todo)) - }, [todo]) - - const [selectedDay, setSelectedDay] = useState(moment()) - - - - - - return ( -
-
- - - -
- ); -} - -export default App; +import './App.css' + +import React, { useEffect } from 'react' + +import Calendar from './components/Calendar/Calendar' +import AddToDo from './components/AddToDo/AddToDo' +import Header from './components/Header/Header' +import ToDoList from './components/ToDoList/ToDoList' + +import { useSelector } from 'react-redux' +import { getTodo } from './redux/selectors/todo-selectors' + +function App() { + const todo = useSelector(getTodo) + + useEffect(() => { + localStorage.setItem('items', JSON.stringify(todo)) + }, [todo]) + + return ( +
+
+ + + +
+ ) +} + +export default React.memo(App) diff --git a/src/components/AddToDo/AddToDo.js b/src/components/AddToDo/AddToDo.js index c95d5bc..341cff3 100644 --- a/src/components/AddToDo/AddToDo.js +++ b/src/components/AddToDo/AddToDo.js @@ -1,77 +1,82 @@ -import { randomColor } from 'randomcolor' -import React, { useState } from 'react'; - -import { v4 as uuid } from 'uuid' - -import './addToDo.css' - -const AddToDo = ({ todo, setToDo, selectedDay }) => { - - const [value, setValue] = useState('') - - - function saveToDo() { - const selectDayStr = selectedDay.format('DDMMYYYY') - if (value.trim()) { - if(todo[selectDayStr]) { - const todoCopy = JSON.parse(JSON.stringify(todo)) - todoCopy[selectDayStr].push({ - id: uuid(), - title: value, - status: true, - defaultPos: defaultPos(), - color: randomColor({ luminosity: 'light' }) - }); - setToDo(todoCopy); - } else { - const todoCopy = JSON.parse(JSON.stringify(todo)) - todoCopy[selectDayStr] = [{ - id: uuid(), - title: value, - status: true, - defaultPos: defaultPos(), - color: randomColor({ luminosity: 'light' }) - }] - setToDo(todoCopy); - } - - - setValue('') - } - } - - function keyPressAddItem(e) { - const code = e.keyCode || e.which - if (code === 13) { - saveToDo() - } - - } - - function defaultPos() { - const width = document.documentElement.clientWidth; - const height = document.documentElement.clientHeight; - const defaultWidth = Math.floor(Math.random() * (width < 300 ? width : width - 300)) + 1 - const defaultHeight = Math.floor(Math.random() * (height < 565 ? height : height - 565)) + 1; - return { x: defaultWidth, y: defaultHeight }; - } - - - return ( -
-
- setValue(e.target.value) } - placeholder="What to do?" - type="text" - onKeyPress={ (e) => keyPressAddItem(e) } - /> - -
-

{selectedDay.format('DD.MM.YYYY')}

-
- ); -}; - -export default AddToDo; \ No newline at end of file +import './addToDo.css' + +import { randomColor } from 'randomcolor' +import React, { useMemo, useState } from 'react' + +import { v4 as uuid } from 'uuid' +import { Button } from '../UI/my-button/index' +import { Input } from '../UI/my-input/index' + +import { fixSize, keyCode } from '../../constants/constants' +import { useDispatch, useSelector } from 'react-redux' +import { addTask } from '../../redux/actions/todos-actions' +import { getSelectedDay } from '../../redux/selectors/todo-selectors' + +const AddToDo = () => { + const [inputValue, setInputValue] = useState('') + + const dispatch = useDispatch() + const selectedDay = useSelector(getSelectedDay) + + function onAddTask(e) { + const code = e.charCode + if (code === keyCode.ENTER) { + addNewTask() + } + } + + function addNewTask() { + const newTask = createNewTask(inputValue) + dispatch(addTask(newTask)) + setInputValue('') + } + + const selectedDateTitle = useMemo( + () => `${selectedDay.slice(0, 2)}.${selectedDay.slice(2, 4)}.${selectedDay.slice(4)}`, + [selectedDay], + ) + + const isDisabledButton = !inputValue.trim() + + return ( +
+
+ setInputValue(e.target.value)} + placeholder="What to do?" + type="text" + onKeyPress={onAddTask} + /> + +
+ {selectedDateTitle} +
+ ) +} + +export default React.memo(AddToDo) + +function getRandomDefaultTaskPosition() { + const width = document.documentElement.clientWidth + const height = document.documentElement.clientHeight + const defaultWidth = Math.floor(Math.random() * (width < fixSize.WIDTH_HEADER ? width : width - fixSize.WIDTH_HEADER)) + const defaultHeight = Math.floor( + Math.random() * (height < fixSize.WIDTH_CALENDAR ? height : height - fixSize.WIDTH_CALENDAR), + ) + + return { x: defaultWidth, y: defaultHeight } +} + +function createNewTask(titleOfNewTask) { + return { + id: uuid(), + title: titleOfNewTask.trim(), + isCompleted: false, + defaultPos: getRandomDefaultTaskPosition(), + color: randomColor({ luminosity: 'light' }), + } +} diff --git a/src/components/AddToDo/addToDo.css b/src/components/AddToDo/addToDo.css index a8a0859..370591b 100644 --- a/src/components/AddToDo/addToDo.css +++ b/src/components/AddToDo/addToDo.css @@ -1,29 +1,31 @@ -.addtodo_div { -display: flex; +.add-todo-block { + display: flex; justify-content: center; } -.addtodo_input { - background-color: inherit; - border:none; - border-bottom: 1px solid white; - color:white; - outline:none; - font-size:16px; +.add-task-container { + display: flex; + flex-direction: column; + align-items: center; } -.addtodo_input:active { - border:none; +.add-todo-input { + font-size: 16px; } -.addtodo_button { +.add-todo-input:active { + border: none; +} + +.add-todo-button { border: 1px solid white; - background-color: inherit; - color:white; - padding: 7px 12px; margin-left: 10px; } -.addtodo_selected-date { + +.add-todo-selected-date { + margin-top: 10px; + margin-bottom: 10px; text-align: center; font-size: 20px; -} \ No newline at end of file +} + diff --git a/src/components/Calendar/Calendar.js b/src/components/Calendar/Calendar.js index 06ad87f..6941a2f 100644 --- a/src/components/Calendar/Calendar.js +++ b/src/components/Calendar/Calendar.js @@ -1,159 +1,33 @@ -import moment from 'moment' -import React, { useState , useEffect } from 'react'; - -import './calendar.css' - -const Calendar = ({ todo, setToDo, setSelectedDay, selectedDay }) => { - - moment.updateLocale('en', { week: { dow: 1 } }); - - - const [currentWindowCalendar, setCurrentWindowCalendar] = useState(setValuesCurrWindow(moment().year(),moment().month())) - const [selectMonthValue, setSelectMonthValue] = useState(moment().month()); - const [selectYearValue, setSelectYearValue] = useState(moment().year()); - - const today = moment(); - - function setValuesCurrWindow(year, month, day = 1) { - const selectedDay = moment().set({ 'year': year, 'month': month, 'date': day }) - const startDay = selectedDay.clone().startOf('month').startOf('week') - let currDay = startDay.subtract(1, 'day').clone(); - const resultArrAllDays = [...Array(42)].map(() => currDay.add(1, 'day').clone()); - - return resultArrAllDays; - } - - - - - const defaultValues = { - years : [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], - monthsNames : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - weekDayNames: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - } - - - - function changeMonthSelect(e) { - setSelectMonthValue(Number(e.target.value)) - } - - function changeYearSelect(e) { - setSelectYearValue(Number(e.target.value)) - } - - useEffect(() => { - setCurrentWindowCalendar(setValuesCurrWindow(selectYearValue, selectMonthValue)) - }, [selectMonthValue, selectYearValue]) - - function todayClickButton() { - setSelectMonthValue(moment().month()) - setSelectYearValue(moment().year()) - setCurrentWindowCalendar(setValuesCurrWindow(moment().year(),moment().month())) - } - - function nextMonthBtn() { - if(selectMonthValue === 11) { - setSelectMonthValue(0) - setSelectYearValue(selectYearValue+1) - } else { - setSelectMonthValue(selectMonthValue+1) - } - } - function prevMonthBtn() { - if(selectMonthValue === 0) { - setSelectMonthValue(11) - setSelectYearValue(selectYearValue-1) - } else { - setSelectMonthValue(selectMonthValue - 1) - } - - } - - - return ( -
-
- {/* BUTTON PREV MONTH */ } - - - - - - - {/* BUTTON NEXT MONTH */ } - -
- -
-
{/* DAYS OF WEEK (MN,FR,SUN) */ } - { - defaultValues.weekDayNames.map((dayOfWeek, index) => { - return
{ dayOfWeek }
- }) - } -
-
{/* DAYS OF MONTH, 42 DAYS */ } - { - currentWindowCalendar.map((dateItem) => { - function addClassDay(item) { - let classes; - if (item.day() === 6 || item.day() === 0) { - classes = 'weekend' - } else { - classes = '' - } - if(dateItem.isSame(selectedDay, 'day')) { - classes = 'selectedDay' - } - - return classes; - } - function addClassDate(dateItem) { - let classesDate = dateItem.isSame(today, 'day') ? 'today' : ''; - if(!(dateItem.month() === selectMonthValue)) { - classesDate += ' notThisMonthColor' - } - - return classesDate; - } - - const dayItemClasses = 'dataItem ' + addClassDay(dateItem) - const dataClasses = addClassDate(dateItem) - - return
{setSelectedDay(dateItem)}} className={ dayItemClasses } key={ dateItem.format('DDMMYYYY') }> -
- { dateItem.format('D') } - -
- {/*{console.log(typeof todo[dateItem.format('DDMMYYYY')])}*/} - {/*{console.log(typeof todo[dateItem.format('DDMMYYYY')] === 'object' ? todo[dateItem.format('DDMMYYYY')].length ? 'haveTaskForThisDay' : '' : '' )}*/} -
- -
- }) - } -
-
- - -
- ); -}; - -export default Calendar; \ No newline at end of file +import './calendar.css' + +import React, { useEffect } from 'react' + +import CalendarNavigation from './CalendarNavigation/CalendarNavigation' +import CalendarTable from './CalendarTable/CalendarTable' +import { createAllDaysForCurrWindow, startWeekFromMonday } from '../utils/data' +import { useDispatch, useSelector } from 'react-redux' +import { changeWindowCalendar } from '../../redux/actions/calendar-actions' +import { mapStateCalendarToProps } from '../../redux/selectors/calendar-selectors' + +function Calendar() { + const dispatch = useDispatch() + const { selectMonthValue, selectYearValue } = useSelector(mapStateCalendarToProps) + + useEffect(() => { + startWeekFromMonday() + dispatch(changeWindowCalendar(createAllDaysForCurrWindow(selectYearValue, selectMonthValue))) + }, []) + + useEffect(() => { + dispatch(changeWindowCalendar(createAllDaysForCurrWindow(selectYearValue, selectMonthValue))) + }, [selectMonthValue, selectYearValue]) + + return ( +
+ + +
+ ) +} + +export default React.memo(Calendar) diff --git a/src/components/Calendar/CalendarNavigation/CalendarNavigation.js b/src/components/Calendar/CalendarNavigation/CalendarNavigation.js new file mode 100644 index 0000000..804acd5 --- /dev/null +++ b/src/components/Calendar/CalendarNavigation/CalendarNavigation.js @@ -0,0 +1,88 @@ +import './calendarNavigation.css' + +import React from 'react' + +import { Button } from '../../UI/my-button/index' +import { Select } from '../../UI/my-select/index' +import { + getCurrentMonth, + getCurrentYear, + getTodayDayId, + monthNamesArr, + yearsValues, + createAllDaysForCurrWindow, +} from '../../utils/data' +import { useDispatch, useSelector } from 'react-redux' +import { changeSelectedDay } from '../../../redux/actions/todos-actions' +import { changeSelectMonth, changeSelectYear, changeWindowCalendar } from '../../../redux/actions/calendar-actions' +import { mapStateCalendarToProps } from '../../../redux/selectors/calendar-selectors' + +function CalendarNavigation() { + const dispatch = useDispatch() + const { selectMonthValue, selectYearValue } = useSelector(mapStateCalendarToProps) + + function onMonthSelectChange(e) { + dispatch(changeSelectMonth(Number(e.target.value))) + } + + function onYearSelectChange(e) { + dispatch(changeSelectYear(Number(e.target.value))) + } + + function onTodayClick() { + dispatch(changeSelectYear(getCurrentYear())) + dispatch(changeSelectMonth(getCurrentMonth())) + dispatch(changeSelectedDay(getTodayDayId())) + dispatch(changeWindowCalendar(createAllDaysForCurrWindow(getCurrentYear(), getCurrentMonth()))) + } + + function onNextMonthClick() { + if (selectMonthValue === 11) { + dispatch(changeSelectMonth(0)) + dispatch(changeSelectYear(selectYearValue + 1)) + } else { + dispatch(changeSelectMonth(selectMonthValue + 1)) + } + } + + function onPreviousMonthClick() { + if (selectMonthValue === 0) { + dispatch(changeSelectMonth(11)) + dispatch(changeSelectYear(selectYearValue - 1)) + } else { + dispatch(changeSelectMonth(selectMonthValue - 1)) + } + } + + return ( +
+ + + + + +
+ ) +} + +export default React.memo(CalendarNavigation) diff --git a/src/components/Calendar/CalendarNavigation/calendarNavigation.css b/src/components/Calendar/CalendarNavigation/calendarNavigation.css new file mode 100644 index 0000000..b91482e --- /dev/null +++ b/src/components/Calendar/CalendarNavigation/calendarNavigation.css @@ -0,0 +1,26 @@ +.calendar-navigation { + display: flex; + justify-content: center; + padding: 15px; +} + +.calendar-button { + font-size: 20px; + border: none; +} + +.calendar-select { + font-size: 20px; + width: 140px; + padding-left: 12px; +} + +.today-button { + border-bottom: 1px solid gray; + border-radius: 10px; +} + +.calendar-select:focus, +.calendar-button:focus { + outline: none; +} \ No newline at end of file diff --git a/src/components/Calendar/CalendarTable/CalendarTable.js b/src/components/Calendar/CalendarTable/CalendarTable.js new file mode 100644 index 0000000..f5cec66 --- /dev/null +++ b/src/components/Calendar/CalendarTable/CalendarTable.js @@ -0,0 +1,72 @@ +import './calendarTable.css' + +import React from 'react' +import { isSelectedDay } from '../../utils/data' +import { useDispatch, useSelector } from 'react-redux' +import { changeSelectedDay } from '../../../redux/actions/todos-actions' +import { mapStateToDoToProps } from '../../../redux/selectors/todo-selectors' +import { getWindowCalendar } from '../../../redux/selectors/calendar-selectors' + +function CalendarTable() { + const dispatch = useDispatch() + const { todo, selectedDay } = useSelector(mapStateToDoToProps) + const currentWindowCalendar = useSelector(getWindowCalendar) + + function onChangeSelectedDay(currDay) { + dispatch(changeSelectedDay(currDay.id)) + } + + return ( + <> +
+
+ {weekDayNames.map((dayOfWeek, index) => { + return ( +
+ {dayOfWeek} +
+ ) + })} +
+
+ {currentWindowCalendar.map((currDay) => { + return ( +
{ + onChangeSelectedDay(currDay) + }} + className={'dataItem ' + isWeekendClass(currDay) + IsSelectedDayClass(currDay, selectedDay)} + key={currDay.id} + > +
{currDay.date.day}
+
+
+ ) + })} +
+
+ + ) +} + +export default React.memo(CalendarTable) + +const weekDayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + +function hasTask(currDay, todo) { + return todo[currDay.id]?.length ? 'haveTaskForThisDay' : '' +} + +function isWeekendClass(currDay) { + return currDay.isWeekend ? 'weekend ' : '' +} + +function isTodayIsThisMonthClasses(currDay) { + let classes = currDay.isToday ? 'today ' : '' + classes += currDay.isCurrentMonth ? '' : 'notThisMonthColor' + return classes +} + +function IsSelectedDayClass(currDay, selectedDay) { + return isSelectedDay(currDay, selectedDay) ? 'selectedDay' : '' +} diff --git a/src/components/Calendar/CalendarTable/calendarTable.css b/src/components/Calendar/CalendarTable/calendarTable.css new file mode 100644 index 0000000..eeb6f2e --- /dev/null +++ b/src/components/Calendar/CalendarTable/calendarTable.css @@ -0,0 +1,63 @@ +.calendar-table-dates { + width: 100%; + display: grid; + grid-template-columns: repeat(7, 1fr); + grid-template-rows: repeat(6, 1fr); + background-color: #404040; + grid-gap: 1px; + border: 1px solid #404040; + margin: auto; +} + +.dataItem { + justify-items: end; + display: grid; + padding: 3px; + color: white; + background-color: #282b30; + min-width: 40px; + min-height: 50px; + font-size: 10px; + cursor: pointer; +} + +.table-daysOfWeek { + justify-items: end; + display: grid; + grid-template-columns: repeat(7, 1fr); + margin: auto; + width: 100%; +} + +.dayOfWeek-item { + min-width: 40px; +} + +.today { + display: grid; + width: 15px; + height: 15px; + background-color: #ef4631; + border-radius: 50%; + justify-content: center; + align-items: center; +} + +.selectedDay { + border: 1px solid cornflowerblue; +} + +.weekend { + background-color: #2e2f36; +} + +.notThisMonthColor { + color: #53565d; +} + +.haveTaskForThisDay { + background-color: blueviolet; + border-radius: 50%; + width: 15px; + height: 15px; +} \ No newline at end of file diff --git a/src/components/Calendar/calendar.css b/src/components/Calendar/calendar.css index 08d18d9..45caae6 100644 --- a/src/components/Calendar/calendar.css +++ b/src/components/Calendar/calendar.css @@ -1,123 +1,9 @@ -.calendar { - -} -.select_date { - display: flex; - justify-content: center; - padding: 15px; - -} - -.calendar_btn { - padding: 5px 10px; - text-align: center; - color:white; - font-size: 20px; - border:none; - background-color: inherit; - cursor: pointer; -} - -.calendar_select { - text-align: center; - color:white; - font-size: 20px; - border:none; - background-color: inherit; - cursor: pointer; -} -.calendar_select option { - background-color: #2e2f36; -} - -.calendar_select_year, .calendar_select_month { - width: 140px; - text-align: start; - padding-left: 12px; -} -.today_btn { - border-bottom: 1px solid gray; - border-radius: 10px; -} - - -.calendar_select:focus, .calendar_btn:focus{ - outline: none; -} - -.calendar_table { +.calendar-table { display: flex; - flex-direction:column; + flex-direction: column; width: 65%; + padding-bottom: 10px; margin: auto; - - - -} - -.calendar_table_dates { - width: 100%; - display: grid; - grid-template-columns: repeat(7,1fr); - grid-template-rows: repeat(6,1fr) ; - background-color: #404040; - grid-gap: 1px; - border: 1px solid #404040; - margin: auto; -} - - -.dataItem { - justify-items: end; - display: grid; - padding: 3px; - color:white; - background-color: #282B30; - min-width: 40px; - min-height: 50px; - font-size: 10px; - cursor: pointer; -} - -.daysOfWeek { - justify-items: end; - display: grid; - grid-template-columns: repeat(7,1fr); - margin: auto; - width: 100%; -} - -.dayOfWeek_Item { - min-width: 40px; - -} - - -.today { - display: grid; - width: 15px; - height: 15px; - background-color: #EF4631; - border-radius: 50%; - justify-content: center; - align-items: center; } -.selectedDay{ -border: 1px solid cornflowerblue; -} - -.weekend { - background-color: #2e2f36; -} - -.notThisMonthColor { - color: #53565D; -} -.haveTaskForThisDay { - background-color: blueviolet; - border-radius: 50%; - width: 15px; - height: 15px; -} \ No newline at end of file diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 8675126..9d40dcf 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,11 +1,41 @@ -import React from 'react'; - -const Header = () => { - return ( -
-

To Do List

-
- ); -}; - -export default Header; \ No newline at end of file +import './header.css' + +import React from 'react' + +import { Button } from '../UI/my-button/index' +import deleteDayTasksSVG from '../../icon/deleteDayTasks.svg' +import deleteAllTasksSVG from '../../icon/deleteAllTasks.svg' +import { useDispatch } from 'react-redux' +import { deleteAllTasks, deleteSelectedDayTasks } from '../../redux/actions/todos-actions' + +function Header() { + const dispatch = useDispatch() + + return ( +
+
+ + +

To Do List

+ +
+
+ ) +} + +export default React.memo(Header) diff --git a/src/components/Header/header.css b/src/components/Header/header.css new file mode 100644 index 0000000..f856924 --- /dev/null +++ b/src/components/Header/header.css @@ -0,0 +1,13 @@ +.header-container { +display: flex; +} +.header-buttons { + border:none; + margin: 5px; +} +.header-title { + text-align: center; + font-size: 24px; + padding-top: 20px; + margin: 0 auto 10px; +} \ No newline at end of file diff --git a/src/components/ListItem/ListItem.js b/src/components/ListItem/ListItem.js deleted file mode 100644 index 2e60f36..0000000 --- a/src/components/ListItem/ListItem.js +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useState } from 'react'; - -import Draggable from 'react-draggable'; - -import iconCross from '../../icon/cross.svg' -import { ReactComponent as Done } from '../../icon/done.svg' -import iconEdit from '../../icon/edit.svg' -import save from '../../icon/save.svg' - -import './listitemstyle.css' - - -const ListItem = ({ todo, setToDo , setSelectedDay, selectedDay}) => { - - const [edit, setEdit] = useState(null) - const [value, setValue] = useState('') - selectedDay = selectedDay.format('DDMMYYYY') - - function deleteToDo(id) { - const todoCopy = JSON.parse(JSON.stringify(todo)) - todoCopy[selectedDay] = [...todo[selectedDay]].filter((obj) => obj.id !== id) - setToDo(todoCopy); - } - - function statusToDo(id) { - const todoCopy = JSON.parse(JSON.stringify(todo)) - todoCopy[selectedDay] = [...todo[selectedDay]].filter((obj) => { - if (obj.id === id) { - obj.status = !obj.status - } - return obj; - }) - setToDo(todoCopy) - } - - function editToDo(id, title) { - setEdit(id) - setValue(title) - } - - function saveToDo(id) { - const todoCopy = JSON.parse(JSON.stringify(todo)) - todoCopy[selectedDay] = [...todo[selectedDay]].map((item) => { - if (item.id === id) { - item.title = value - } - return item; - }) - setToDo(todoCopy); - setEdit(null); - } - - function updatePosition(data, index) { - const todoCopy = JSON.parse(JSON.stringify(todo)) - todoCopy[selectedDay][index].defaultPos = { x: data.x, y: data.y } - setToDo(todoCopy) - } - - - - - return ( -
- - { todo[selectedDay] ? - todo[selectedDay].map((item, index) => ( - - { - updatePosition(data, index) - } } - - > -
- { - edit === item.id ? -
- { index + 1 }. - setValue((e.target.value)) } - value={ value } - - /> -
: -
{ index + 1 }. { item.title }
- } - { - - edit === item.id ? -
- -
: - -
- - - - -
- - - } - - -
-
- )) - : -
- Tasks not found -
- } -
- - ); -}; - -export default ListItem; \ No newline at end of file diff --git a/src/components/ToDoList/ToDoItem/EditTask/EditTask.js b/src/components/ToDoList/ToDoItem/EditTask/EditTask.js new file mode 100644 index 0000000..17dc246 --- /dev/null +++ b/src/components/ToDoList/ToDoItem/EditTask/EditTask.js @@ -0,0 +1,34 @@ +import React from 'react' +import { Input } from '../../../UI/my-input/index' +import { Button } from '../../../UI/my-button/index' +import save from '../../../../icon/save.svg' +import { useDispatch } from 'react-redux' +import { updateTask } from '../../../../redux/actions/todos-actions' + +function EditTask({ value, item, index, setEditingId, onInputChange }) { + const dispatch = useDispatch() + + function saveToDo(id, value) { + const newTitle = { title: value } + dispatch(updateTask(id, newTitle)) + setEditingId(null) + } + + function handlerTitleChange(e) { + onInputChange(e.target.value) + } + + return ( +
+
+ {index + 1}. + +
+ +
+ ) +} + +export default React.memo(EditTask) diff --git a/src/components/ToDoList/ToDoItem/EditTask/editTask.css b/src/components/ToDoList/ToDoItem/EditTask/editTask.css new file mode 100644 index 0000000..e69de29 diff --git a/src/components/ToDoList/ToDoItem/ShowTask/ShowTask.js b/src/components/ToDoList/ToDoItem/ShowTask/ShowTask.js new file mode 100644 index 0000000..55f8087 --- /dev/null +++ b/src/components/ToDoList/ToDoItem/ShowTask/ShowTask.js @@ -0,0 +1,36 @@ +import React from 'react' + +import { Button } from '../../../UI/my-button/index' +import iconCross from '../../../../icon/cross.svg' +import iconEdit from '../../../../icon/edit.svg' +import { ReactComponent as Done } from '../../../../icon/done.svg' +import { useDispatch } from 'react-redux' +import { deleteTask, updateTask } from '../../../../redux/actions/todos-actions' + +function ShowTask({ item, index, editToDo }) { + const dispatch = useDispatch() + + return ( +
+
+ {index + 1}. {item.title} +
+
+ + + +
+
+ ) +} + +export default React.memo(ShowTask) diff --git a/src/components/ToDoList/ToDoItem/ShowTask/showTask.css b/src/components/ToDoList/ToDoItem/ShowTask/showTask.css new file mode 100644 index 0000000..e69de29 diff --git a/src/components/ListItem/listitemstyle.css b/src/components/ToDoList/ToDoItem/ToDoItem.css similarity index 56% rename from src/components/ListItem/listitemstyle.css rename to src/components/ToDoList/ToDoItem/ToDoItem.css index 927dcda..2300a2e 100644 --- a/src/components/ListItem/listitemstyle.css +++ b/src/components/ToDoList/ToDoItem/ToDoItem.css @@ -1,41 +1,41 @@ -.list_item { - display: flex; - position: absolute; - width: 300px; - min-height: 60px; - justify-content: space-between; - align-items: center; - color:black; - font-weight: bold; - padding:5px; - border-radius: 10px; - cursor: move; - margin: auto; - -} -.listitem_btn { - background-color: inherit; - border:none; - cursor: pointer; - -} - -.list_item-input-change { - margin-left: 3px; - background-color: inherit; - border:none; - border-bottom: 1px solid black; - font-weight: bold; - color:black; - outline:none; - font-size:16px; - font-family: 'Montserrat', sans-serif; -} -.list_item-title-nochange { - font-size:16px; -} - -.item_done { - text-decoration: line-through; -} - +.list-item { + display: flex; + position: absolute; + width: 300px; + min-height: 60px; + justify-content: space-between; + align-items: center; + color: black; + font-weight: bold; + padding: 5px; + border-radius: 10px; + cursor: move; + margin: auto; +} + +.list-item-input-change { + margin-left: 3px; + background-color: inherit; + border: none; + border-bottom: 1px solid black; + font-weight: bold; + color: black; + outline: none; + font-size: 16px; + font-family: "Montserrat", sans-serif; +} + +.list-item-title-nochange { + font-size: 16px; +} + +.item-done { + text-decoration: line-through; +} + +.list-item-button { + background-color: inherit; + border: none; + cursor: pointer; + padding: 1px 6px; +} \ No newline at end of file diff --git a/src/components/ToDoList/ToDoItem/ToDoItem.js b/src/components/ToDoList/ToDoItem/ToDoItem.js new file mode 100644 index 0000000..200d7f7 --- /dev/null +++ b/src/components/ToDoList/ToDoItem/ToDoItem.js @@ -0,0 +1,58 @@ +import './ToDoItem.css' + +import Draggable from 'react-draggable' +import React, { useCallback, useState } from 'react' + +import EditTask from './EditTask/EditTask' +import ShowTask from './ShowTask/ShowTask' +import { useDispatch } from 'react-redux' +import { updateTask } from '../../../redux/actions/todos-actions' + +function ToDoItem({ item, index }) { + const [editingId, setEditingId] = useState(null) + const [titleChangeInput, setTitleChangeInput] = useState('') + + const dispatch = useDispatch() + + const editToDo = useCallback((id, title) => { + setEditingId(id) + setTitleChangeInput(title) + }, []) + + const handlerTitleChange = useCallback((newTitle) => { + setTitleChangeInput(newTitle) + }, []) + + function onStopDraggable(e, data) { + dispatch(updateTask(item.id, { defaultPos: { x: data.x, y: data.y } })) + } + + return ( +
+ { + onStopDraggable(e, data) + }} + > +
+ {editingId === item.id ? ( + + ) : ( + + )} +
+
+
+ ) +} + +export default React.memo(ToDoItem) diff --git a/src/components/ToDoList/ToDoList.js b/src/components/ToDoList/ToDoList.js new file mode 100644 index 0000000..c520264 --- /dev/null +++ b/src/components/ToDoList/ToDoList.js @@ -0,0 +1,23 @@ +import './ToDolist.css' + +import React from 'react' + +import ToDoItem from './ToDoItem/ToDoItem' +import { useSelector } from 'react-redux' +import { getSelectedDayTasks } from '../../redux/selectors/todo-selectors' + +function ToDoList() { + const selectedDayTasks = useSelector(getSelectedDayTasks) + + return ( +
+ {selectedDayTasks?.length ? ( + selectedDayTasks.map((item, index) => ) + ) : ( +
Tasks not found
+ )} +
+ ) +} + +export default React.memo(ToDoList) diff --git a/src/components/ToDoList/ToDolist.css b/src/components/ToDoList/ToDolist.css new file mode 100644 index 0000000..af12360 --- /dev/null +++ b/src/components/ToDoList/ToDolist.css @@ -0,0 +1,11 @@ +.draggable-element { + width: 300px; + min-height: 60px; + position: absolute; +} + +.header-taskNotFound { + text-align: center; +} + + diff --git a/src/components/UI/my-button/index.js b/src/components/UI/my-button/index.js new file mode 100644 index 0000000..9c7dc58 --- /dev/null +++ b/src/components/UI/my-button/index.js @@ -0,0 +1,27 @@ +import './myButton.css' + +import React from 'react' +import ReactTooltip from 'react-tooltip' + +export const Button = (props) => { + const classNames = 'myButton-default-style ' + props.className + + if (props.tooltip) { + const tooltipType = props['tooltip-type'] ? props['tooltip-type'] : 'error' + + return ( + + ) + } + + return ( + + ) +} diff --git a/src/components/UI/my-button/myButton.css b/src/components/UI/my-button/myButton.css new file mode 100644 index 0000000..07c3743 --- /dev/null +++ b/src/components/UI/my-button/myButton.css @@ -0,0 +1,11 @@ +.myButton-default-style { + padding: 5px 10px; + text-align: center; + color: white; + background-color: inherit; + cursor: pointer; +} +button:disabled { + opacity: 50%; + cursor: default; +} \ No newline at end of file diff --git a/src/components/UI/my-input/index.js b/src/components/UI/my-input/index.js new file mode 100644 index 0000000..79a1aae --- /dev/null +++ b/src/components/UI/my-input/index.js @@ -0,0 +1,13 @@ +import './myInput.css' + +import React from 'react' + +export const Input = (props) => { + const classNames = 'myInput-default-style ' + props.className + + return ( + + {props.children} + + ) +} diff --git a/src/components/UI/my-input/myInput.css b/src/components/UI/my-input/myInput.css new file mode 100644 index 0000000..2664858 --- /dev/null +++ b/src/components/UI/my-input/myInput.css @@ -0,0 +1,7 @@ +.myInput-default-style { + background-color: inherit; + border: none; + border-bottom: 1px solid white; + color: white; + outline: none; +} \ No newline at end of file diff --git a/src/components/UI/my-select/index.js b/src/components/UI/my-select/index.js new file mode 100644 index 0000000..9dad99c --- /dev/null +++ b/src/components/UI/my-select/index.js @@ -0,0 +1,21 @@ +import './mySelect.css' + +import React from 'react' + +export const Select = ({ optionsObj, children, ...props }) => { + const classNames = 'mySelect-default-style ' + props.className + + return ( + + ) +} diff --git a/src/components/UI/my-select/mySelect.css b/src/components/UI/my-select/mySelect.css new file mode 100644 index 0000000..6a364dc --- /dev/null +++ b/src/components/UI/my-select/mySelect.css @@ -0,0 +1,10 @@ +.mySelect-default-style { + text-align: start; + color: white; + border: none; + background-color: inherit; + cursor: pointer; +} +.mySelect-default-style option { + background-color: #2e2f36; +} \ No newline at end of file diff --git a/src/components/utils/data.js b/src/components/utils/data.js new file mode 100644 index 0000000..00576a1 --- /dev/null +++ b/src/components/utils/data.js @@ -0,0 +1,111 @@ +import moment from 'moment' + +export const monthNamesArr = moment()._locale._months + +export function isSelectedDay(dayObj, selectedDay) { + return dayObj.id === selectedDay +} +export function startWeekFromMonday() { + moment.updateLocale('en', { week: { dow: 1 } }) +} + +export function getTodayDayId() { + return moment().format('DDMMYYYY') +} + +export function getCurrentYear() { + return moment().year() +} + +export function getCurrentMonth() { + return moment().month() +} + +export const isWeekend = (momentDay) => { + return momentDay.isoWeekday() === 6 || momentDay.isoWeekday() === 7 +} + +export function getDayOfMonth(momentDay) { + return momentDay.date() +} + +export function getMonthOfYear(momentDay) { + return momentDay.month() + 1 +} + +export function getYear(momentDay) { + return momentDay.year() +} + +export function isCurrentMonth(momentDay, selectMonth) { + return momentDay.month() === selectMonth +} + +export function getId(momentDay) { + return momentDay.format('DDMMYYYY') +} + +export function isToday(momentDay) { + return momentDay.isSame(moment(), 'day') +} + +export function createOneDay(newDay, selectMonthValue) { + return { + id: getId(newDay), + date: { + day: getDayOfMonth(newDay), + month: getMonthOfYear(newDay), + year: getYear(newDay), + }, + isWeekend: isWeekend(newDay), + isCurrentMonth: isCurrentMonth(newDay, selectMonthValue), + isToday: isToday(newDay), + } +} + +export function createAllDaysForCurrWindow(year, month, day = 1) { + const selectedDay = moment().set({ year: year, month: month, date: day }) + const startDay = selectedDay.clone().startOf('month').startOf('week') + const currDay = startDay.subtract(1, 'day').clone() + const resultArrAllDays = [...Array(42)].map(() => createOneDay(currDay.add(1, 'day').clone(), month)) + + return resultArrAllDays +} + +export const yearsValues = { + 2016: 2016, + 2017: 2017, + 2018: 2018, + 2019: 2019, + 2020: 2020, + 2021: 2021, + 2022: 2022, + 2023: 2023, + 2024: 2024, + 2025: 2025, + 2026: 2026, +} + +// export const createDayMetods = { +// getMonthOfYear: function getMonthOfYear(momentDay) { +// return momentDay.month() + 1 +// }, +// getYear: function getYear(momentDay) { +// return momentDay.year() +// }, +// isCurrentMonth: function isCurrentMonth(momentDay, selectMonth) { +// return momentDay.month() === selectMonth +// }, +// getId: function getId(momentDay) { +// return momentDay.format('DDMMYYYY') +// }, +// isToday: function isToday(momentDay) { +// return momentDay.isSame(moment(), 'day') +// }, +// isWeekend: function isWeekend(momentDay) { +// return momentDay.isoWeekday() === 6 || momentDay.isoWeekday() === 7 +// }, +// getDayOfMonth: function getDayOfMonth(momentDay) { +// return momentDay.date() +// }, +// } diff --git a/src/constants/actions-types/calendar.js b/src/constants/actions-types/calendar.js new file mode 100644 index 0000000..b5e70ad --- /dev/null +++ b/src/constants/actions-types/calendar.js @@ -0,0 +1,3 @@ +export const CHANGE_SELECT_MONTH = 'CHANGE_SELECT_MONTH' +export const CHANGE_SELECT_YEAR = 'CHANGE_SELECT_YEAR' +export const CHANGE_WINDOW_CALENDAR = 'CHANGE_WINDOW_CALENDAR' diff --git a/src/constants/actions-types/index.js b/src/constants/actions-types/index.js new file mode 100644 index 0000000..a8f6238 --- /dev/null +++ b/src/constants/actions-types/index.js @@ -0,0 +1,2 @@ +export * from './todos' +export * from './calendar' diff --git a/src/constants/actions-types/todos.js b/src/constants/actions-types/todos.js new file mode 100644 index 0000000..c9624e4 --- /dev/null +++ b/src/constants/actions-types/todos.js @@ -0,0 +1,8 @@ +export const ADD_TASK = 'ADD_TASK' +export const DELETE_TASK = 'DELETE_TASK' +export const UPDATE_TASK = 'UPDATE_TASK' + +export const DELETE_SELECTED_DAY_TASKS = 'DELETE_SELECTED_DAY_TASKS' +export const DELETE_ALL_TASKS = 'DELETE_ALL_TASKS' + +export const CHANGE_SELECTED_DAY = 'CHANGE_SELECTED_DAY' diff --git a/src/constants/constants.js b/src/constants/constants.js new file mode 100644 index 0000000..b1c097c --- /dev/null +++ b/src/constants/constants.js @@ -0,0 +1,8 @@ +export const keyCode = { + ENTER: 13, + TAB: 9, +} +export const fixSize = { + WIDTH_HEADER: 300, + WIDTH_CALENDAR: 580, +} diff --git a/src/icon/deleteAllTasks.svg b/src/icon/deleteAllTasks.svg new file mode 100644 index 0000000..647c816 --- /dev/null +++ b/src/icon/deleteAllTasks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icon/deleteDayTasks.svg b/src/icon/deleteDayTasks.svg new file mode 100644 index 0000000..90e2394 --- /dev/null +++ b/src/icon/deleteDayTasks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/index.js b/src/index.js index e1e164e..c900898 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,23 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; - - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - -); - +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +import thunk from 'redux-thunk' +import { createStore, compose, applyMiddleware } from 'redux' +import { rootReducer } from './redux/reducers' +import { Provider } from 'react-redux' + +const store = createStore( + rootReducer, + compose( + applyMiddleware(thunk), + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), //<-- для redux devTools`a + ), +) + +const root = ReactDOM.createRoot(document.getElementById('root')) +root.render( + + + , +) diff --git a/src/redux/actions/calendar-actions.js b/src/redux/actions/calendar-actions.js new file mode 100644 index 0000000..068c13f --- /dev/null +++ b/src/redux/actions/calendar-actions.js @@ -0,0 +1,22 @@ +import { CHANGE_SELECT_MONTH, CHANGE_SELECT_YEAR, CHANGE_WINDOW_CALENDAR } from '../../constants/actions-types/' + +export function changeSelectMonth(newMonth) { + return { + type: CHANGE_SELECT_MONTH, + data: newMonth, + } +} + +export function changeSelectYear(newYear) { + return { + type: CHANGE_SELECT_YEAR, + data: newYear, + } +} + +export function changeWindowCalendar(newWindowCalendar) { + return { + type: CHANGE_WINDOW_CALENDAR, + data: newWindowCalendar, + } +} diff --git a/src/redux/actions/todos-actions.js b/src/redux/actions/todos-actions.js new file mode 100644 index 0000000..ca4513a --- /dev/null +++ b/src/redux/actions/todos-actions.js @@ -0,0 +1,48 @@ +import { + ADD_TASK, + CHANGE_SELECTED_DAY, + DELETE_ALL_TASKS, + DELETE_SELECTED_DAY_TASKS, + DELETE_TASK, + UPDATE_TASK, +} from '../../constants/actions-types/' + +export function addTask(newTask) { + return { + type: ADD_TASK, + data: { newTask }, + } +} + +export function deleteTask(task) { + return { + type: DELETE_TASK, + data: task, + } +} + +export function updateTask(taskId, changes) { + return { + type: UPDATE_TASK, + data: { taskId, changes }, + } +} + +export function changeSelectedDay(date) { + return { + type: CHANGE_SELECTED_DAY, + data: date, + } +} + +export function deleteSelectedDayTasks() { + return { + type: DELETE_SELECTED_DAY_TASKS, + } +} + +export function deleteAllTasks() { + return { + type: DELETE_ALL_TASKS, + } +} diff --git a/src/redux/reducers/calendar/index.js b/src/redux/reducers/calendar/index.js new file mode 100644 index 0000000..b63f1a1 --- /dev/null +++ b/src/redux/reducers/calendar/index.js @@ -0,0 +1,24 @@ +import * as DateUtils from '../../../components/utils/data' +import { CHANGE_SELECT_MONTH, CHANGE_SELECT_YEAR, CHANGE_WINDOW_CALENDAR } from '../../../constants/actions-types' + +const initialState = { + selectMonthValue: DateUtils.getCurrentMonth(), + selectYearValue: DateUtils.getCurrentYear(), + currentWindowCalendar: [], +} + +export const calendarReducer = (state = initialState, action) => { + switch (action.type) { + case CHANGE_SELECT_MONTH: + return { ...state, selectMonthValue: action.data } + + case CHANGE_SELECT_YEAR: + return { ...state, selectYearValue: action.data } + + case CHANGE_WINDOW_CALENDAR: + return { ...state, currentWindowCalendar: action.data } + + default: + return state + } +} diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js new file mode 100644 index 0000000..a92cc9b --- /dev/null +++ b/src/redux/reducers/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux' +import { todosReducer } from './todos' +import { calendarReducer } from './calendar' + +export const rootReducer = combineReducers({ + todosReducer, + calendarReducer, +}) diff --git a/src/redux/reducers/todos/index.js b/src/redux/reducers/todos/index.js new file mode 100644 index 0000000..c72fbe2 --- /dev/null +++ b/src/redux/reducers/todos/index.js @@ -0,0 +1,62 @@ +import { + ADD_TASK, + CHANGE_SELECTED_DAY, + DELETE_ALL_TASKS, + DELETE_SELECTED_DAY_TASKS, + DELETE_TASK, + UPDATE_TASK, +} from '../../../constants/actions-types' +import { getTodayDayId } from '../../../components/utils/data' + +const initialState = { + todo: JSON.parse(localStorage.getItem('items')) || {}, + selectedDay: getTodayDayId(), +} + +export const todosReducer = (state = initialState, action) => { + const { todo, selectedDay } = state + switch (action.type) { + case ADD_TASK: { + const { newTask } = action.data + const selectedDayTasks = todo[selectedDay] + const newDayTask = selectedDayTasks ? [...selectedDayTasks, newTask] : [newTask] + + return { ...state, todo: { ...todo, [selectedDay]: newDayTask } } + } + + case DELETE_TASK: { + const task = action.data + const newSelectDayToDo = todo[selectedDay].filter(({ id }) => { + return id !== task.id + }) + + return { ...state, todo: { ...todo, [selectedDay]: newSelectDayToDo } } + } + + case UPDATE_TASK: { + const { taskId, changes } = action.data + const newSelectedDayToDo = todo[selectedDay].map((task) => { + if (task.id !== taskId) { + return task + } + return { ...task, ...changes } + }) + return { ...state, todo: { ...todo, [selectedDay]: newSelectedDayToDo } } + } + + case DELETE_SELECTED_DAY_TASKS: { + return { ...state, todo: { ...todo, [selectedDay]: {} } } + } + + case DELETE_ALL_TASKS: { + return { ...state, todo: {} } + } + + case CHANGE_SELECTED_DAY: { + return { ...state, selectedDay: action.data } + } + + default: + return state + } +} diff --git a/src/redux/selectors/calendar-selectors.js b/src/redux/selectors/calendar-selectors.js new file mode 100644 index 0000000..791fcb2 --- /dev/null +++ b/src/redux/selectors/calendar-selectors.js @@ -0,0 +1,10 @@ +export const getSelectMonth = (state) => state.calendarReducer.selectMonthValue +export const getSelectYear = (state) => state.calendarReducer.selectYearValue +export const getWindowCalendar = (state) => state.calendarReducer.currentWindowCalendar +export const mapStateCalendarToProps = (state) => { + return { + selectMonthValue: getSelectMonth(state), + selectYearValue: getSelectYear(state), + currentWindowCalendar: getWindowCalendar(state), + } +} diff --git a/src/redux/selectors/todo-selectors.js b/src/redux/selectors/todo-selectors.js new file mode 100644 index 0000000..4b4228e --- /dev/null +++ b/src/redux/selectors/todo-selectors.js @@ -0,0 +1,6 @@ +export const getTodo = (state) => state.todosReducer.todo +export const getSelectedDay = (state) => state.todosReducer.selectedDay +export const getSelectedDayTasks = (state) => getTodo(state)[getSelectedDay(state)] +export const mapStateToDoToProps = (state) => { + return { todo: getTodo(state), selectedDay: getSelectedDay(state) } +}