diff --git a/package-lock.json b/package-lock.json index cb6f20995cd7290456862eed47a271e5bc0d3b0b..9454140e8330bd5e3eb814e7ee9ab913540ab3fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1298,6 +1298,11 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -1617,6 +1622,19 @@ "hoist-non-react-statics": "^3.3.0" } }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/node": { + "version": "14.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", + "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==" + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -1830,6 +1848,11 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -1918,6 +1941,39 @@ "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -2483,6 +2539,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "autobind-decorator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", + "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==" + }, "autoprefixer": { "version": "9.8.0", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.0.tgz", @@ -3330,6 +3391,99 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3868,6 +4022,11 @@ } } }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -3918,6 +4077,14 @@ "shallow-clone": "^0.1.2" } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -4112,6 +4279,45 @@ } } }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + } + } + }, "confusing-browser-globals": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", @@ -4309,6 +4515,11 @@ "randomfill": "^1.0.3" } }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -4862,6 +5073,14 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4875,6 +5094,11 @@ "regexp.prototype.flags": "^1.2.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4918,6 +5142,11 @@ "strip-bom": "^2.0.0" } }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -5252,6 +5481,11 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -5472,6 +5706,11 @@ "is-symbol": "^1.0.2" } }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -7660,6 +7899,14 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" }, + "global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "requires": { + "ini": "^1.3.5" + } + }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -7716,6 +7963,24 @@ } } }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -7867,6 +8132,11 @@ } } }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -8106,6 +8376,11 @@ } } }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -8486,6 +8761,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, "image-to-base64": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/image-to-base64/-/image-to-base64-2.1.1.tgz", @@ -8531,6 +8811,11 @@ } } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, "import-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", @@ -8971,6 +9256,11 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -9616,6 +9906,22 @@ "merge-stream": "^1.0.1" } }, + "jodit": { + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/jodit/-/jodit-3.4.29.tgz", + "integrity": "sha512-1rW4aBeG5hgdTjNxYOWN2hc51M5O4U3BNdwxaxwLR8pIlrjmeDVxZu2YZ6L4TI1MfFRCpczL0vPzj6hlTFyvng==", + "requires": { + "autobind-decorator": "^2.4.0" + } + }, + "jodit-react": { + "version": "1.0.66", + "resolved": "https://registry.npmjs.org/jodit-react/-/jodit-react-1.0.66.tgz", + "integrity": "sha512-qGuQituwzJU9DP9bbo9ACYrRAKQPHbCfCw0xelEeLRkLX7Z2eJn+qs+BmmA+sq2BCzam1MPl853DuClkfIIedw==", + "requires": { + "jodit": "^3.4.29" + } + }, "joi": { "version": "11.4.0", "resolved": "https://registry.npmjs.org/joi/-/joi-11.4.0.tgz", @@ -9696,6 +10002,11 @@ "resolved": "https://registry.npmjs.org/jsmin/-/jsmin-1.0.1.tgz", "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=" }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -9872,6 +10183,19 @@ "walker": "1.x" } }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -9907,6 +10231,14 @@ "webpack-sources": "^1.1.0" } }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "requires": { + "package-json": "^6.3.0" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -10143,6 +10475,11 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -10420,6 +10757,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "mini-create-react-context": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", @@ -10731,6 +11073,152 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==" }, + "nodemailer": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.14.tgz", + "integrity": "sha512-0AQHOOT+nRAOK6QnksNaK7+5vjviVvEBzmZytKU7XSA+Vze2NLykTx/05ti1uJgXFTWrMq08u3j3x4r4OE6PAA==" + }, + "nodemon": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.6.tgz", + "integrity": "sha512-4I3YDSKXg6ltYpcnZeHompqac4E6JeAMpGm8tJnB9Y3T0ehasLa4139dJOcCrB93HHrUMsCrKtoAlXTqT5n4AQ==", + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -11081,6 +11569,11 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -11122,6 +11615,24 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -11307,8 +11818,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "optional": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { "version": "2.3.0", @@ -13741,6 +14251,11 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", @@ -13867,6 +14382,11 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -13922,6 +14442,14 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "requires": { + "escape-goat": "^2.0.0" + } + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -14017,6 +14545,17 @@ } } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "react": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", @@ -14253,6 +14792,16 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz", "integrity": "sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q==" }, + "react-event-listener": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", + "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, "react-fine-uploader": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/react-fine-uploader/-/react-fine-uploader-1.1.1.tgz", @@ -14338,6 +14887,11 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-moment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-moment/-/react-moment-1.0.0.tgz", + "integrity": "sha512-J4iIiwUT4oZcL7cp2U7naQKbQtqvmzGXXBMg/DLj+Pi7n9EW0VhBRx/1aJ1Tp2poCqTCAPoadLEoUIkReGnNNg==" + }, "react-recaptcha": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/react-recaptcha/-/react-recaptcha-2.3.10.tgz", @@ -14500,6 +15054,85 @@ "react": "^16.1.0" } }, + "react-swipeable-views": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.13.9.tgz", + "integrity": "sha512-WXC2FKYvZ9QdJ31v9LjEJEl1bA7E4AcaloTkbW0uU0dYf5uvv4aOpiyxubvOkVl1a5L2UAHmKSif4TmJ9usrSg==", + "requires": { + "@babel/runtime": "7.0.0", + "prop-types": "^15.5.4", + "react-swipeable-views-core": "^0.13.7", + "react-swipeable-views-utils": "^0.13.9", + "warning": "^4.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "react-swipeable-views-core": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.13.7.tgz", + "integrity": "sha512-ekn9oDYfBt0oqJSGGwLEhKvn+QaqMGTy//9dURTLf+vp7W5j6GvmKryYdnwJCDITaPFI2hujXV4CH9krhvaE5w==", + "requires": { + "@babel/runtime": "7.0.0", + "warning": "^4.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "react-swipeable-views-utils": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.13.9.tgz", + "integrity": "sha512-QLGxRKrbJCbWz94vkWLzb1Daaa2Y/TZKmsNKQ6WSNrS+chrlfZ3z9tqZ7YUJlW6pRWp3QZdLSY3UE3cN0TXXmw==", + "requires": { + "@babel/runtime": "7.0.0", + "keycode": "^2.1.7", + "prop-types": "^15.6.0", + "react-event-listener": "^0.6.0", + "react-swipeable-views-core": "^0.13.7", + "shallow-equal": "^1.2.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -14511,6 +15144,15 @@ "prop-types": "^15.6.2" } }, + "react-window": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.6.tgz", + "integrity": "sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==", + "requires": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -14948,6 +15590,22 @@ "unicode-match-property-value-ecmascript": "^1.2.0" } }, + "registry-auth-token": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", + "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "requires": { + "rc": "^1.2.8" + } + }, "regjsgen": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", @@ -15133,6 +15791,14 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -15597,6 +16263,21 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -15776,6 +16457,11 @@ } } }, + "shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -16717,6 +17403,11 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" + }, "terser": { "version": "3.17.0", "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", @@ -16954,6 +17645,11 @@ "kind-of": "^3.0.2" } }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", @@ -16997,6 +17693,14 @@ "hoek": "4.x.x" } }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -17065,6 +17769,11 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -17079,6 +17788,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", @@ -17089,6 +17806,24 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.5.tgz", "integrity": "sha1-S1v/+Rhu/7qoiOTJ6UvZ/EyUkp0=" }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -17215,6 +17950,84 @@ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, + "update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", @@ -17268,6 +18081,14 @@ "requires-port": "^1.0.0" } }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -18084,6 +18905,49 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -18373,6 +19237,11 @@ "async-limiter": "~1.0.0" } }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index 22c837b427c03bfe990bf426d6fcddca56b69ad9..ecaa7e76090d1859e9e555e9a3ebe403d4469362 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,15 @@ "react-icons": "^3.11.0", "react-image-crop": "^8.6.6", "react-input-mask": "^2.0.4", + "react-moment": "^1.0.0", "react-recaptcha": "^2.3.10", "react-responsive-carousel": "^3.2.10", "react-router-dom": "^5.1.2", "react-router-hash-link": "^2.3.1", "react-scripts": "^2.1.8", "react-star-ratings": "^2.3.0", + "react-swipeable-views": "^0.13.9", + "react-window": "^1.8.6", "styled-components": "^4.4.1", "typescript": "^3.9.7", "use-cookie-state": "^1.0.0" diff --git a/src/Admin/Components/Components/AlertDialog.js b/src/Admin/Components/Components/AlertDialog.js new file mode 100644 index 0000000000000000000000000000000000000000..14861d979b42a3a78cdeeb07ed501e4615dc4e43 --- /dev/null +++ b/src/Admin/Components/Components/AlertDialog.js @@ -0,0 +1,56 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; + +const AlertDialog = ( props ) => { + return ( + <div> + <Dialog + open={props.open} + onClose={props.HandleClose} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title"> + Deseja deletar o dado de id: {props.deleteItem.id} + </DialogTitle> + <DialogContent> + <DialogContentText id="alert-dialog-description"> + Se você deletar essa dado, todas as informações desse dado serão deletas para sempre + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={props.HandleClose} color="primary"> + Não deletar + </Button> + <Button onClick={props.OnDelete} color="secondary" autoFocus> + Deletar + </Button> + </DialogActions> + </Dialog> + </div> + ); +} + +export default AlertDialog; \ No newline at end of file diff --git a/src/Admin/Components/Components/DataCard.js b/src/Admin/Components/Components/DataCard.js new file mode 100644 index 0000000000000000000000000000000000000000..9f5c565d6374808b1a0abc830cdfaba9e805881a --- /dev/null +++ b/src/Admin/Components/Components/DataCard.js @@ -0,0 +1,140 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState } from "react"; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../Styles/DataCard"; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +import ArrowBackIosIcon from "@material-ui/icons/ArrowBackIos"; + +const DataCard = (params) => { + console.log(params) + return( + <div> + {params.data} + </div> + ) + // const classes = useStyles(); + // const [edit, setEdit] = useState(false); + + // const buttonArr = [ + // { + // label: "Listar", + // icon: <ListRoundedIcon />, + // }, + // edit + // ? { + // label: "Voltar", + // icon: <ArrowBackIosIcon />, + // } + // : { + // label: "Editar", + // icon: <EditRoundedIcon />, + // }, + // ]; + + // //Change state of the var edit + // const EditHandler = () => { + // setEdit(!edit); + // }; + + // // it stores an function that will be called when a button is clicked + // const functionArr = [props.viewData, EditHandler]; + + // return ( + // edit ? + // <Card className={classes.root} variant="outlined"> + // <CardContent> + // <div className={classes.displayRow}> + // <Typography + // className={classes.title} + // color="inherit" + // gutterBottom + // > + // {props.data[1].prop} + // </Typography> + // <ButtonGroup + // color="primary" + // aria-label="outlined primary button group" + // > + // {buttonArr.map((button, index) => ( + // <Button onClick={functionArr[index]}> + // <div className={classes.displayRow}> + // {button.icon} + // {button.label} + // </div> + // </Button> + // ))} + // </ButtonGroup> + // </div> + + // {/* Display the edit area */} + // {props.component} + + // </CardContent> + // </Card> + // : + // <Card className={classes.root} variant="outlined"> + // <CardContent> + // <div className={classes.displayRow}> + // <Typography + // className={classes.title} + // color="inherit" + // gutterBottom + // > + // {props.data[1].prop} + // </Typography> + // <ButtonGroup + // color="primary" + // aria-label="outlined primary button group" + // > + // {buttonArr.map((button, index) => ( + // <Button onClick={functionArr[index]}> + // <div className={classes.displayRow}> + // {button.icon} + // {button.label} + // </div> + // </Button> + // ))} + // </ButtonGroup> + // </div> + // {props.data.map((info , index) => ( + // <div className={classes.displayColumn} key={index}> + // <Typography color="initial" className={classes.subTitle}> + // {info.subTitle} + // </Typography> + // <Typography color='textSecondary'> + // { + // info.prop === null ? 'Sem dados' : info.prop + // } + // </Typography> + // </div> + // ))} + // </CardContent> + // </Card> + // ); +} + +export default DataCard; diff --git a/src/Admin/Components/Components/DataCards/ActivityCard.js b/src/Admin/Components/Components/DataCards/ActivityCard.js new file mode 100644 index 0000000000000000000000000000000000000000..7b5eba7d6284b5d407c068fcead648600d7caf46 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/ActivityCard.js @@ -0,0 +1,155 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../../Styles/DataCard"; +import Grid from "@material-ui/core/Grid"; +//imports from local files +import { GetAData } from "../../../Filters"; +import { GetSpecificData } from '../../../Services'; +import { Link } from 'react-router-dom' +import LoadingSpinner from '../../../../Components/LoadingSpinner'; + +const ActivityCard = ({ match }) => { + console.log(match); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({}); //Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + //getting data from server + useEffect(() => { + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetSpecificData(GetAData("activities", match.params.id), headers).then( + (res) => { + if (res.state) { + setItem(res.data); + setIsLoaded(true); + setError(false); + } else { + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + console.log(item) + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "DONO(A)", + prop: item.owner === null ? '' : item.owner.name + }, + { + subTitle: "Trackable type", + prop: item["trackable_type"], + }, + { + subTitle: "Atividade", + prop: item.activity, + }, + { + subTitle: "Privacidade", + prop: item.privacy, + }, + { + subTitle: "Criado em", + prop: DisplayDate(item["created_at"]), + }, + { + subTitle: "Recipient type", + prop: item["recipient_type"], + }, + + ]; + + return ( + <Card> + <CardContent> + <Grid container xs={12} justify="space-between" alignItems="center" alignContent="center"> + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.id} + </Typography> + </Grid> + <Grid item> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link style={{textDecoration: 'none'}} to={`/admin/activities`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } +}; + +export default ActivityCard; diff --git a/src/Admin/Components/Components/DataCards/CollectionCard.js b/src/Admin/Components/Components/DataCards/CollectionCard.js new file mode 100644 index 0000000000000000000000000000000000000000..2657bc5c95c7aa40c2d64c200195f14fd8875401 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/CollectionCard.js @@ -0,0 +1,168 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../../Styles/DataCard"; +import Grid from '@material-ui/core/Grid'; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +//imports from local files +import { GetAData } from "../../../Filters"; +import { GetSpecificData } from '../../../Services'; +import { Link } from 'react-router-dom' +import LoadingSpinner from '../../../../Components/LoadingSpinner'; + +const CollectionCard = ({ match }) => { + console.log(match); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({}); //Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + useEffect(() => { + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetSpecificData(GetAData("collections", match.params.id), headers).then( + (res) => { + if (res.state) { + setItem(res.data); + setIsLoaded(true); + setError(false); + } else { + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "Nome", + prop: item.name, + }, + { + subTitle: "Descrição", + prop: item.description, + }, + { + subTitle: "Endereço", + prop: item.owner ? item.owner.name : null, + }, + { + subTitle: "Score", + prop: item.score, + }, + { + subTitle: "Privacidade", + prop: item.privacy, + }, + { + subTitle: "Criação", + prop: DisplayDate(item.created_at), + }, + { + subTitle: "Atualização", + prop: DisplayDate(item.updated_at), + }, + ]; + + return ( + <Card> + <CardContent> + <Grid xs={12} justify="space-between" alignItems="center" container> + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.name} + </Typography> + </Grid> + <Grid item> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link style={{ textDecoration: 'none' }} to={`/admin/Collections`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + + <Link style={{ textDecoration: 'none' }} to={`/admin/EditCollection/${item.id}`}> + <Button + startIcon={<EditRoundedIcon />} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } +}; + +export default CollectionCard; diff --git a/src/Admin/Components/Components/DataCards/CommunityQuestionCard.js b/src/Admin/Components/Components/DataCards/CommunityQuestionCard.js new file mode 100644 index 0000000000000000000000000000000000000000..4e7bafaf512da4e52990d85b2675f26a0029b8c2 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/CommunityQuestionCard.js @@ -0,0 +1,174 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useContext } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import Grid from "@material-ui/core/Grid"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../../Styles/DataCard"; +// Icons +import EmailRoundedIcon from '@material-ui/icons/EmailRounded'; +//imports from local files +import { GetAData } from "../../../Filters"; +import { Store } from '../../../../Store'; +import { Link } from 'react-router-dom' +import { GetSpecificData } from "../../../Services"; +import Unauthorized from "../Unauthorized"; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; + +const CommunityQuestions = ({ match }) => { + const { state, dispatch } = useContext(Store); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({});//Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + useEffect(() => { + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetSpecificData(GetAData("contacts", match.params.id), headers).then( + (res) => { + if (res.state) { + setItem(res.data); + setIsLoaded(true); + setError(false); + } else { + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if(CheckUserPermission()){ + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "Nome", + prop: item.name, + }, + { + subTitle: "Email", + prop: + item.email ? + <Link to={`/admin/sendEmail/${item.email}`} style={{textDecoration : 'none'}}> + <Button + variant='text' + color='primary' + startIcon={<EmailRoundedIcon />} + > + {item.email} + </Button> + </Link> : null + }, + { + subTitle: "Mensagem", + prop: item.message, + }, + { + subTitle: "Criado em", + prop: DisplayDate(item.created_at), + }, + ]; + + return ( + <Card> + <CardContent> + <Grid direction="row" justify="space-between" alignContent="center" alignItems="center" container> + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.name} + </Typography> + </Grid> + <Grid> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link style={{textDecoration: 'none'}} to={`/admin/CommunityQuestions`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } else return <Unauthorized/> +}; + +export default CommunityQuestions; diff --git a/src/Admin/Components/Components/DataCards/ComplaintsCard.js b/src/Admin/Components/Components/DataCards/ComplaintsCard.js new file mode 100644 index 0000000000000000000000000000000000000000..cbf6c800e781231e1ff89b2213f3812d91d4d77a --- /dev/null +++ b/src/Admin/Components/Components/DataCards/ComplaintsCard.js @@ -0,0 +1,730 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import Paper from "@material-ui/core/Paper"; +import Grid from "@material-ui/core/Grid"; +import Divider from "@material-ui/core/Divider"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import GetAppRoundedIcon from "@material-ui/icons/GetAppRounded"; +import LaunchRoundedIcon from "@material-ui/icons/LaunchRounded"; +import RemoveRoundedIcon from "@material-ui/icons/RemoveRounded"; +import RestoreRoundedIcon from "@material-ui/icons/RestoreRounded"; +import { useStyles } from "../../Styles/DataCard"; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import CircularProgress from '@material-ui/core/CircularProgress'; +//imports from local files +import { GetAData, MethodsToComplain } from "../../../Filters"; +import { GetSpecificData, HandleComplain } from "../../../Services"; +import { Link } from "react-router-dom"; +import { CardActions } from "@material-ui/core"; +import { apiDomain } from '../../../../env'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +import SnackBar from '../../../../Components/SnackbarComponent'; +//styles + +const PORTAL_MEC = "https://plataformaintegrada.mec.gov.br/"; + +const CollectionCard = ({ match }) => { + const classes = useStyles(); + + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({}); //Necessary to consult the API, data + const [complainedObject, setComplainedObject] = useState(null);//stores the data from complained object + const [contentModal, setContentModal] = useState(""); + const [titleModal, setTitleModal] = useState(""); + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const [loadingObject, setLoadingObject] = useState(false); + const [open, setOpen] = React.useState(false); + const [scroll, setScroll] = React.useState('paper'); + + const handleClose = () => { + setOpen(false); + }; + + const descriptionElementRef = React.useRef(null); + React.useEffect(() => { + if (open) { + const { current: descriptionElement } = descriptionElementRef; + if (descriptionElement !== null) { + descriptionElement.focus(); + } + } + }, [open]); + + const convertToLink = (type, id) => { + switch (type) { + case "LearningObject": + return `recurso?id=${id}/`; + case "User": + return `usuario-publico/${id}/`; + default: + return ""; + } + }; + + const handleClickOpen = (scrollType) => async () => { + setLoadingObject(true); + setOpen(true); + setScroll(scrollType); + + if (!complainedObject) { + let headers = { + "Accept": "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + "client": sessionStorage.getItem("@portalmec/clientToken"), + "uid": sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetSpecificData(GetAData("learning_objects", item.complainable_id), headers).then( + (res) => { + if (res.state) { + HandleContentModal(res.data); + setComplainedObject(res.data) + } else { + HandleContentModal(); + } + setLoadingObject(false); + } + ); + } else { + HandleContentModal(complainedObject); + setLoadingObject(false); + } + }; + + const ComplaintStatus = (status) => { + switch (status) { + case "accepted": + return ( + <Paper + className={classes.marginTop} + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FA8072", + fontWeight: "500", + color: "#FFFAFA", + }} + > + REMOVIDO + </Paper> + ); + case "complained": + return ( + <Paper + className={classes.marginTop} + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FF8C00", + fontWeight: "500", + color: "#FFFAFA", + }} + > + PENDENTE + </Paper> + ); + case "rejected": + return ( + <Paper + className={classes.marginTop} + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#228B22", + fontWeight: "500", + color: "#FFFAFA", + }} + > + AVALIADO + </Paper> + ); + default: + return "NOTHING"; + } + }; + + const Actions = (status) => { + switch (status) { + case "accepted": + return ( + "Este recurso já foi avaliado" + ); + case "complained": + return ( + <> + <Grid + container + className={classes.marginTop} + direction="row" + justify="space-between" + alignItems="center" + > + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + variant="h6" + > + Remover o recurso e notificar o autor + </Typography> + </Grid> + <Grid item> + <Button + style={{ + color: "#FFFAFA", + backgroundColor: "#FA8072", + fontWeight: "500", + }} + variant="contained" + onClick={() => HandleComplainSubmmit("accept")} + startIcon={ + <RemoveRoundedIcon style={{ fill: "#FFFAFA" }} /> + } + > + Remover + </Button> + </Grid> + </Grid> + + <Divider className={classes.marginTop} /> + + <Grid + container + className={classes.marginTop} + direction="row" + justify="space-between" + alignItems="center" + > + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + variant="h6" + > + Reativar o recurso na plataforma + </Typography> + </Grid> + <Grid item> + <Button + style={{ + color: "#FFFAFA", + backgroundColor: "#228B22", + fontWeight: "500", + }} + variant="contained" + onClick={() => HandleComplainSubmmit("reject")} + startIcon={ + <RestoreRoundedIcon style={{ fill: "#FFFAFA" }} /> + } + > + Ativar + </Button> + </Grid> + </Grid> + </> + ); + case "rejected": + return ( + "Este recurso já foi avaliado" + ); + default: + return "NOTHING"; + } + } + + const HandleContentModal = (data) => { + const status = item.state; + const ok = data ? true : false; + + if (ok) { + const importantData = [ + { + subTitle: "ID", + prop: data.id, + }, + { + subTitle: "Nome", + prop: data.name, + }, + { + subTitle: "Descrição", + prop: data.description, + }, + { + subTitle: "Dono(a)", + prop: data.publisher.name, + }, + { + subTitle: "Autor(a)", + prop: data.author, + }, + { + subTitle: "Score", + prop: data.score, + }, + { + subTitle: "Status", + prop: data.state, + }, + { + subTitle: "Criado em", + prop: data.created_ats + }, + { + subTitle: "Atualizado em", + prop: DisplayDate(data.updated_at) + }, + { + subTitle: "Criado em", + prop: DisplayDate(data.created_at) + }, + { + subTitle: "Visualizações", + prop: data.views_count, + }, + { + subTitle: "Likes", + prop: data.likes_count, + }, + { + subTitle: "Avaliação média", + prop: data.review_average, + }, + { + subTitle: "Likes", + prop: data.likes_count, + }, + { + subTitle: "Linguagem", + prop: ( + <div + style={{ + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "Space-between", + }} + > + {data.language.map((lan, index) => ( + <Paper + elevation={3} + key={index} + style={{ + borderRadius: 18, + padding: "0.8em", + marginBottom: "1em", + marginRight: "1em", + backgroundColor: "#D3D3D3", + fontSize: 14, + }} + > + {lan.name} + </Paper> + ))} + </div> + ), + }, + { + subTitle: "Tipo de recurso", + prop: data.object_type, + }, + ] + + setContentModal( + <Grid container> + <Grid item justify="flex-start" alignItems="center" alignContent="center"> + {data.thumbnail ? ( + <Grid item style={{ marginTop: "1em", marginBottom: "1em", alignSelf: "center" }}> + <a target="_blank" href={apiDomain + data.thumbnail}> + {" "} + <img src={apiDomain + data.thumbnail} alt="" />{" "} + </a> + </Grid> + ) : null} + {importantData.map((info, index) => ( + <Grid item className={classes.displayColumn} key={index}> + <Typography color="textPrimary"> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </Grid> + ))} + </Grid> + </Grid> + ) + setTitleModal(data.name) + + } else { + setTitleModal("IndisponÃvel"); + setContentModal("Não foi possÃvel encontrar!") + } + } + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData).format("LLL").toString(); + } + + const HandleComplainSubmmit = async (method) => { + HandleComplain(MethodsToComplain("complaints", match.params.id, method)).then((res) => { + if (res) { + HandleSnack('Alteração feito com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + }) + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + useEffect(() => { + let headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetSpecificData(GetAData("complaints", match.params.id), headers).then( + (res) => { + if (res.state) { + setItem(res.data) + setIsLoaded(true); + setError(false); + } else { + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "State", + prop: item.state, + }, + { + subTitle: "Descrição", + prop: item.description, + }, + { + subTitle: "Status", + prop: item.complaint_reason.status, + }, + { + subTitle: "Motivo", + prop: item.complaint_reason.reason, + }, + { + subTitle: "Criado", + prop: DisplayDate(item.created_at), + }, + { + subTitle: "User", + prop: item.user_id, + }, + ]; + + return ( + <Grid + container + spacing={3} + sm={12} + xs={12} + > + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <Grid + item + sm={8} + xs={12} + > + <Card> + <CardContent> + <Grid container justify="space-between"> + <Grid item> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Denuncia: + </Typography> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Recurso #{item.complainable_id} + </Typography> + </Grid> + <Grid item> + <Link + style={{ textDecoration: "none" }} + to={`/admin/complaints`} + > + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + <CardActions> + <Button + variant="contained" + color="primary" + startIcon={<GetAppRoundedIcon />} + onClick={handleClickOpen('paper')} + > + Veja o recurso + </Button> + + <Button + style={{ + fontWeight: "500", + backgroundColor: "#FA8072" + }} + variant="contained" + startIcon={<LaunchRoundedIcon style={{ fill: "#FFFAFA" }} />} + > + <a + style={{ textDecoration: "none", color: "#FFFAFA" }} + target="_blank" + href={ + PORTAL_MEC + + convertToLink( + item.complainable_type, + item.complainable_id + ) + } + > + MEC RED + </a> + </Button> + </CardActions> + </Card> + </Grid> + + <Grid + item + sm={4} + xs={12} + > + <Grid item> + <Card> + <CardContent> + <Typography variant="h5" component="h2"> + Denúncia #{item.id} + </Typography> + + {ComplaintStatus(item.state)} + + <Grid container className={classes.marginTop}> + <Grid item> + <Typography + color="textSecondary" + className={classes.subTitle} + variant="h6" + > + Descrição + </Typography> + <Typography color="initial">{item.description}</Typography> + </Grid> + </Grid> + + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + className={classes.marginTop} + > + <Grid item> + <Typography + color="textSecondary" + className={classes.subTitle} + > + Data + </Typography> + <Typography color="initial">{item.created_at}</Typography> + </Grid> + + <Grid item> + <Typography + color="textSecondary" + className={classes.subTitle} + > + Denunciante + </Typography> + <Typography color="initial">{item.user_id}</Typography> + </Grid> + </Grid> + + <Divider className={classes.marginTop} /> + + <Grid container className={classes.marginTop}> + <Grid item> + <Typography + color="textSecondary" + className={classes.subTitle} + variant="h6" + > + Motivo + </Typography> + <Typography color="initial"> + {item.complaint_reason.reason} + </Typography> + </Grid> + </Grid> + + <Grid container className={classes.marginTop}> + <Grid item> + <Typography + color="textSecondary" + className={classes.subTitle} + variant="h6" + > + Status + </Typography> + <Typography color="initial"> + {item.complaint_reason.status} + </Typography> + </Grid> + </Grid> + </CardContent> + </Card> + </Grid> + + <Grid item className={classes.marginTop}> + <Card> + <CardContent> + <Typography variant="h5" component="h2"> + Ações + </Typography> + { + Actions(item.state) + } + </CardContent> + </Card> + </Grid> + </Grid> + + <Dialog + open={open} + onClose={handleClose} + scroll={scroll} + aria-labelledby="scroll-dialog-title" + aria-describedby="scroll-dialog-description" + > + <DialogTitle id="scroll-dialog-title"> + { + loadingObject ? <CircularProgress size={24} /> : titleModal + } + </DialogTitle> + <DialogContent dividers={scroll === 'paper'}> + <DialogContentText + id="scroll-dialog-description" + ref={descriptionElementRef} + tabIndex={-1} + > + { + loadingObject ? <CircularProgress size={24} /> : contentModal + } + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={handleClose} color="primary"> + Fechar + </Button> + </DialogActions> + </Dialog> + </Grid> + ); + } +}; + +export default CollectionCard; diff --git a/src/Admin/Components/Components/DataCards/EducationalObjectsCard.js b/src/Admin/Components/Components/DataCards/EducationalObjectsCard.js new file mode 100644 index 0000000000000000000000000000000000000000..3cd89ced2a4ddb2a1c2d325897dfcc5e30d0ac53 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/EducationalObjectsCard.js @@ -0,0 +1,256 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import Paper from "@material-ui/core/Paper"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import EditRoundedIcon from '@material-ui/icons/EditRounded'; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../../Styles/DataCard"; +//imports from local files +import { GetAData } from "../../../Filters"; +import { Link } from "react-router-dom"; +import { GetSpecificData } from "../../../Services"; +import { apiUrl, apiDomain } from "../../../../env"; +import { Grid } from "@material-ui/core"; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; + +const CommunityQuestions = ({ match }) => { + console.log(match); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({});//Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + useEffect(() => { + GetSpecificData(GetAData("learning_objects", match.params.id)).then( + (res) => { + if (res.state) { + setItem(res.data); + setIsLoaded(true); + setError(false); + } else { + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + console.log(item); + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "Nome", + prop: item.name, + }, + { + subTitle: "Descrição", + prop: item.description, + }, + { + subTitle: "Dono(a)", + prop: item.publisher.name, + }, + { + subTitle: "Autor(a)", + prop: item.author, + }, + { + subTitle: "Score", + prop: item.score, + }, + { + subTitle: "Status", + prop: item.state, + }, + { + subTitle: "Criado em", + prop: DisplayDate(item.created_at), + }, + { + subTitle: "Atualizado em", + prop: DisplayDate(item.updated_at), + }, + { + subTitle: "Visualizações", + prop: item.views_count, + }, + { + subTitle: "Likes", + prop: item.likes_count, + }, + { + subTitle: "Avaliação média", + prop: item.review_average, + }, + { + subTitle: "Likes", + prop: item.likes_count, + }, + { + subTitle: "Linguagem", + prop: ( + <div + style={{ + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "Space-between", + }} + > + {item.language.map((lan, index) => ( + <Paper + elevation={3} + key={index} + style={{ + borderRadius: 18, + padding: "0.8em", + marginBottom: "1em", + marginRight: "1em", + backgroundColor: "#D3D3D3", + fontSize: 14, + }} + > + {lan.name} + </Paper> + ))} + </div> + ), + }, + { + subTitle: "Tipo de recurso", + prop: item.object_type, + }, + ]; + + return ( + <Card variant="outlined"> + <CardContent> + <Grid + container + xs={12} + display="row" + justify="space-between" + alignItems="center" + alignContent="space-between" + > + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.name} + </Typography> + </Grid> + <Grid item> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link + style={{ textDecoration: "none" }} + to={`/admin/learningObjects`} + > + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + <Button + startIcon={<VisibilityIcon />} + color="primary" + variant="outlined" + > + <a + style={{ textDecoration: "none" }} + target="_blank" + href={ + apiUrl + + "/learning_objects/" + + match.params.id + + "/download" + } + > + Ver recurso + </a> + </Button> + <Link + style={{ textDecoration: "none" }} + to={`/admin/learningObjectEdit/${item.id}`} + > + <Button + startIcon={<EditRoundedIcon />} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {item.thumbnail ? ( + <div style={{ marginTop: "1em", marginBottom: "1em" }}> + <a target="_blank" href={apiDomain + item.thumbnail}> + {" "} + <img src={apiDomain + item.thumbnail} alt="" />{" "} + </a> + </div> + ) : null} + + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } +}; + +export default CommunityQuestions; diff --git a/src/Admin/Components/Components/DataCards/InstitutionsCard.js b/src/Admin/Components/Components/DataCards/InstitutionsCard.js new file mode 100644 index 0000000000000000000000000000000000000000..a918cb9d8e4f4d3b139201c433c525256b10a3d1 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/InstitutionsCard.js @@ -0,0 +1,163 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useContext } from "react"; +import moment from 'moment' +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../../Styles/DataCard"; +import Grid from '@material-ui/core/Grid'; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +//imports from local files +import { GetAData } from "../../../Filters"; +import { Store } from '../../../../Store'; +import { Link } from 'react-router-dom'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; + +const InstitutionCard = ({ match }) => { + console.log(match); + + const { state, dispatch } = useContext(Store); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + + const [item, setItem] = useState({}); //Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + useEffect(() => { + fetch(GetAData('institutions', match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setItem(result); + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "Nome", + prop: item.name, + }, + { + subTitle: "Descrição", + prop: item.description, + }, + { + subTitle: "Endereço", + prop: item.address, + }, + { + subTitle: "Cidade", + prop: item.city, + }, + { + subTitle: "PaÃs", + prop: item.country, + }, + { + subTitle: "Criação", + prop: DisplayDate(item.created_at), + }, + { + subTitle: "Atualizado", + prop: DisplayDate(item.updated_at), + }, + ]; + + return ( + <Card> + <CardContent> + <Grid container xs={12} justify="space-between" alignItems="center" alignContent="center"> + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.name} + </Typography> + </Grid> + <Grid item> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link style={{textDecoration: 'none'}} to={`/admin/intitutions`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + + <Link style={{textDecoration: 'none'}} to={`/admin/institutionEdit/${item.id}`}> + <Button + startIcon={<EditRoundedIcon/>} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } +}; + +export default InstitutionCard; diff --git a/src/Admin/Components/Components/DataCards/NoteVarCard.js b/src/Admin/Components/Components/DataCards/NoteVarCard.js new file mode 100644 index 0000000000000000000000000000000000000000..546a270d4d9fb9de3f85706510590675f1445785 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/NoteVarCard.js @@ -0,0 +1,152 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import Grid from "@material-ui/core/Grid"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import { useStyles } from "../../Styles/DataCard"; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +//imports from local files +import { GetAData } from "../../../Filters"; +import { Link } from 'react-router-dom'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; + +const NoteCard = ({ match }) => { + console.log(match); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({}); //Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + + useEffect(() => { + fetch(GetAData('scores', match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setItem(result); + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "Nome", + prop: item.name, + }, + { + subTitle: "Código", + prop: item.code, + }, + { + subTitle: "Peso", + prop: item.weight, + }, + { + subTitle: "Criação", + prop: DisplayDate(item.created_at), + }, + { + subTitle: "Atualizado", + prop: DisplayDate(item.updated_at), + }, + ] + + return ( + <Card> + <CardContent> + <Grid container xs={12} justify="space-between" alignContent="center" alignItems="center"> + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.name} + </Typography> + </Grid> + <Grid item> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link style={{textDecoration: 'none'}} to={`/admin/noteVars`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + + <Link style={{textDecoration: 'none'}} to={`/admin/noteVarEdit/${item.id}`}> + <Button + startIcon={<EditRoundedIcon/>} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } +}; + +export default NoteCard; diff --git a/src/Admin/Components/Components/DataCards/RatingCard.js b/src/Admin/Components/Components/DataCards/RatingCard.js new file mode 100644 index 0000000000000000000000000000000000000000..b610182b04a5f9ad8724587cd886f484f27aed53 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/RatingCard.js @@ -0,0 +1,147 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import ButtonGroup from "@material-ui/core/ButtonGroup"; +import Grid from "@material-ui/core/Grid"; +import { useStyles } from "../../Styles/DataCard"; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +//imports from local files +import { GetAData } from "../../../Filters"; +import { Link } from 'react-router-dom'; +import LoadingSpinner from '../../../../Components/LoadingSpinner';; + +const RatingCard = ({ match }) => { + console.log(match); + const classes = useStyles(); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [item, setItem] = useState({}); //Necessary to consult the API, data + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + useEffect(() => { + fetch(GetAData('ratings', match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setItem(result); + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + const DATA = [ + { + subTitle: "ID", + prop: item.id, + }, + { + subTitle: "Nome", + prop: item.name, + }, + { + subTitle: "Descrição", + prop: item.description, + }, + { + subTitle: "Criado em", + prop: DisplayDate(item.created_at), + }, + { + subTitle: "Atualizado em", + prop: DisplayDate(item.updated_at), + }, + ]; + + return ( + <Card variant="outlined"> + <CardContent> + <Grid container xs={12} justify="space-between" alignItems="center" alignContent="center"> + <Grid item> + <Typography className={classes.title} color="inherit" gutterBottom> + {item.name} + </Typography> + </Grid> + <Grid item> + <ButtonGroup + color="primary" + aria-label="outlined primary button group" + > + <Link style={{textDecoration: 'none'}} to={`/admin/Ratings`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + + <Link style={{textDecoration: 'none'}} to={`/admin/EditRating/${item.id}`}> + <Button + startIcon={<EditRoundedIcon/>} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + </ButtonGroup> + </Grid> + </Grid> + {DATA.map((info, index) => ( + <div className={classes.displayColumn} key={index}> + <Typography color="initial" className={classes.subTitle}> + {info.subTitle} + </Typography> + <Typography color="textSecondary"> + {info.prop === null ? "Sem dados" : info.prop} + </Typography> + </div> + ))} + </CardContent> + </Card> + ); + } +}; + +export default RatingCard; diff --git a/src/Admin/Components/Components/DisplayIcon.js b/src/Admin/Components/Components/DisplayIcon.js new file mode 100644 index 0000000000000000000000000000000000000000..0bb9bf56e0a44cfdd256b9172bb14079d8ff8664 --- /dev/null +++ b/src/Admin/Components/Components/DisplayIcon.js @@ -0,0 +1,77 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React from "react"; +import ListItemIcon from "@material-ui/core/ListItemIcon"; +import HomeIcon from "@material-ui/icons/Home"; +import PeopleRoundedIcon from "@material-ui/icons/PeopleRounded"; +import ContactSupportRoundedIcon from "@material-ui/icons/ContactSupportRounded"; +import LanguageRoundedIcon from "@material-ui/icons/LanguageRounded"; +import AccountBalanceRoundedIcon from "@material-ui/icons/AccountBalanceRounded"; +import MenuBookRoundedIcon from "@material-ui/icons/MenuBookRounded"; +import StarRoundedIcon from "@material-ui/icons/StarRounded"; +import AccountCircleRoundedIcon from "@material-ui/icons/AccountCircleRounded"; +import TrendingUpRoundedIcon from "@material-ui/icons/TrendingUpRounded"; +import HelpRoundedIcon from "@material-ui/icons/HelpRounded"; +import CheckRoundedIcon from "@material-ui/icons/CheckRounded"; +import PersonRoundedIcon from "@material-ui/icons/PersonRounded"; +import BlockRoundedIcon from "@material-ui/icons/BlockRounded"; +import AnnouncementRoundedIcon from "@material-ui/icons/AnnouncementRounded"; +import EmailRoundedIcon from "@material-ui/icons/EmailRounded"; +import TimelineRoundedIcon from "@material-ui/icons/TimelineRounded"; +import SettingsRoundedIcon from "@material-ui/icons/SettingsRounded"; +import ExitToAppRoundedIcon from "@material-ui/icons/ExitToAppRounded"; +import AllOutIcon from "@material-ui/icons/AllOut"; + +//This file manipulate the icon that will be displayed in the left navigation menu + +const orange = "#ff7f00"; +const pink = "#e81f4f"; +const purple = "#673ab7"; +const blue = "#00bcd4"; + +const icons = [ + <HomeIcon style={{ fill: orange }} />, + <PeopleRoundedIcon style={{ fill: pink }} />, + <AllOutIcon style={{ fill: purple }} />, + <ContactSupportRoundedIcon style={{ fill: blue }} />, + <AccountBalanceRoundedIcon style={{ fill: orange }} />, + <LanguageRoundedIcon style={{ fill: pink }} />, + <MenuBookRoundedIcon style={{ fill: purple }} />, + <StarRoundedIcon style={{ fill: blue }} />, + <AccountCircleRoundedIcon style={{ fill: orange }} />, + <TrendingUpRoundedIcon style={{ fill: pink }} />, + <HelpRoundedIcon style={{ fill: purple }} />, + <CheckRoundedIcon style={{ fill: blue }} />, + <PersonRoundedIcon style={{ fill: orange }} />, + <BlockRoundedIcon style={{ fill: pink }} />, + <AnnouncementRoundedIcon style={{ fill: purple }} />, + <EmailRoundedIcon style={{ fill: blue }} />, + <TimelineRoundedIcon style={{ fill: orange }} />, + <SettingsRoundedIcon style={{ fill: pink }} />, + <ExitToAppRoundedIcon style={{ fill: purple }} />, +]; + +const DisplayIcon = (props) => { + return( + <ListItemIcon> + {icons[props.i]} + </ListItemIcon> + ); +}; +export default DisplayIcon; diff --git a/src/Admin/Components/Components/Inputs/CreateInstitution.js b/src/Admin/Components/Components/Inputs/CreateInstitution.js new file mode 100644 index 0000000000000000000000000000000000000000..20ad3c141fc670d50c268f390bcc0f13e5f9170e --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateInstitution.js @@ -0,0 +1,284 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useContext } from 'react'; +//imports material ui componets +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +//imports local files +import { apiUrl } from '../../../../env'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import Unauthorized from '../Unauthorized'; +//imports services +import { Create } from '../../../Services'; +//router +import {Link} from 'react-router-dom'; + +const CreateInstitution = (props) => { + const { state, dispatch } = useContext(Store); + + const [name, setName] = useState('Nova Instituição'); + const [description, setDescription] = useState(''); + const [adress, setAdress] = useState(''); + const [city, setCity] = useState(''); + const [country, setCountry] = useState(''); + + const [isLoading, setIsLoading] = useState(false) + + // Handle error in name + const [errorInName, setErrorInName] = useState({ + error: false, + message: '', + }) + + // Handle error in Country + const [errorInCountry, setErrorInCountry] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + setName(e.target.value) + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + } + const DescriptionHandler = (e) => { + setDescription(e.target.value) + } + const AdressHandler = (e) => { + setAdress(e.target.value) + } + const CityHandler = (e) => { + setCity(e.target.value) + } + const CountryHandler = (e) => { + if (errorInCountry.error) { + setErrorInCountry({ + error: false, + message: '' + }) + } + setCountry(e.target.value) + } + + // verify if the given text is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + //Handle submit + async function onSubmit() { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(country)) { + const api = apiUrl + '/institutions' + const body = { + "institution": { + 'name': name, + 'description': description, + 'address': adress, + 'city': city, + 'country': country, + } + } + Create(api, body).then(res => { + if (res) { + HandleSnack('A instituição foi criada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Esse campo está vazio' + }) + } + if (isEmpty(country)) { + setErrorInCountry({ + error: true, + message: 'Esse campo está vazio' + }) + } + setIsLoading(false) + } + + } + + // Fields + const fields = [ + { + label: 'Nome', + value: name, + required: true, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event) + }, + { + label: 'Descrição', + value: description, + required: false, + + onChange: (event) => DescriptionHandler(event) + }, + { + label: 'Endereço', + value: adress, + required: false, + + onChange: (event) => AdressHandler(event) + }, + { + label: 'Cidade', + value: city, + required: false, + onChange: (event) => CityHandler(event) + }, + { + label: 'PaÃs', + value: country, + required: true, + error: errorInCountry.error, + errorMessage: errorInCountry.message, + onChange: (event) => CountryHandler(event) + } + ] + + if(CheckUserPermission()){ + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' alignContent="center" alignItems="center" xs={12}> + <Grid item> + <Typography variant='h4'> + {name} + </Typography> + </Grid> + <Grid item> + <Link style={{textDecoration: 'none'}} to={'/admin/intitutions'}> + <Button + onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Adicionar' + } + </Button> + </CardAction> + </Card> + ); + } else return <Unauthorized/> +} + +export default CreateInstitution; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/CreateLanguage.js b/src/Admin/Components/Components/Inputs/CreateLanguage.js new file mode 100644 index 0000000000000000000000000000000000000000..8b72fffd47555868109fac25cc852b83576f183c --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateLanguage.js @@ -0,0 +1,249 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useContext } from 'react'; +//imports material ui componets +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +//imports local files +import { apiUrl } from '../../../../env'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +//imports services +import { Create } from '../../../Services'; +//router +import { Link } from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const CreateLanguage = (props) => { + const { state, dispatch } = useContext(Store); + + const [name, setName] = useState('Nova linguagem'); + const [code, setCode] = useState(''); + + const [isLoading, setIsLoading] = useState(false) + + // Handle error in name + const [errorInName, setErrorInName] = useState({ + error: false, + message: '', + }) + + // Handle error in Country + const [errorInCode, setErrorInCode] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + setName(e.target.value) + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + } + + const CodeHandler = (e) => { + if (errorInCode.error) { + setErrorInCode({ + error: false, + message: '' + }) + } + setCode(e.target.value) + } + + // verify if the given text is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + //Handle submit + async function onSubmit() { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(code)) { + const api = apiUrl + '/languages' + const body = { + "language": { + 'name': name, + 'code': code, + } + } + Create(api, body).then(res => { + if (res) { + HandleSnack('A linguagem foi criada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Esse campo está vazio' + }) + } + if (isEmpty(code)) { + setErrorInCode({ + error: true, + message: 'Esse campo está vazio' + }) + } + setIsLoading(false) + } + } + + // Fields + const fields = [ + { + label: 'Nome', + value: name, + required: true, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event) + }, + { + label: 'Código', + value: code, + required: true, + error: errorInCode.error, + errorMessage: errorInCode.message, + onChange: (event) => CodeHandler(event) + } + ] + + if (CheckUserPermission()) { + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' alignItems="center" alignContent="center" xs={12}> + <Grid item> + <Typography variant='h4'> + {name} + </Typography> + </Grid> + <Grid item> + <Link to={'/admin/languages'} style={{ textDecoration: 'none' }}> + <Button + onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Adicionar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized/> +} + +export default CreateLanguage; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/CreateQuestion.js b/src/Admin/Components/Components/Inputs/CreateQuestion.js new file mode 100644 index 0000000000000000000000000000000000000000..6a4a6640713a1963b38ce4bed887feac1545ff3e --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateQuestion.js @@ -0,0 +1,274 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useContext } from 'react'; +//imports material ui componets +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import MenuItem from "@material-ui/core/MenuItem"; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +//imports local files +import { apiUrl } from '../../../../env'; +import { Store } from '../../../../Store'; +import Unauthorized from '../Unauthorized'; +import SnackBar from '../../../../Components/SnackbarComponent'; +//imports services +import { Create } from '../../../Services'; +//router +import { Link } from 'react-router-dom'; + +const CreateQuestion = (props) => { + const { state, dispatch } = useContext(Store); + + const [status, setStatus] = useState(''); + const [description, setDescription] = useState(''); + + const [isLoading, setIsLoading] = useState(false) + + // Handle error in name + const [errorInStatus, setErrorInStatus] = useState({ + error: false, + message: '', + }) + + // Handle error in Country + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const DescriptionHandler = (e) => { + if (errorInDescription.error) { + setErrorInDescription({ + error: false, + message: '' + }) + } + setDescription(e.target.value) + } + + // verify if the given text is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + //Handle submit + async function onSubmit() { + setIsLoading(true) + if (!isEmpty(status) && !isEmpty(description)) { + const api = apiUrl + '/questions' + const body = { + "question": { + 'status': status, + 'description': description, + } + } + Create(api, body).then(res => { + if (res) { + HandleSnack('A pergunta foi criada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(status)) { + setErrorInStatus({ + error: true, + message: 'Esse campo está vazio' + }) + } + if (isEmpty(description)) { + setErrorInDescription({ + error: true, + message: 'Esse campo está vazio' + }) + } + setIsLoading(false) + } + } + + //handle change of staus + + const handleChange = (e) => { + const value = e.target.value; + setStatus(value); + console.log(status) + }; + + // Fields + const fields = [ + { + label: 'Descrição', + value: description, + required: true, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event) + } + ] + + const STATUS_OPTIONS = [ + { + value: "active", + label: "Ativo", + }, + { + value: "inactive", + label: "Inativo", + }, + ]; + + if(CheckUserPermission()) { + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' alignContent="center" alignItems="center" xs={12}> + <Grid item> + <Typography variant='h4'> + Nova question + </Typography> + </Grid> + <Grid item> + <Link to={'/admin/Questions'} style={{ textDecoration: 'none' }}> + <Button + onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + <> + <TextField + select + label="Status" + value={status ? status : ""} + error={errorInStatus.error} + style={{ width: '250px', marginBottom: '1em' }} + helperText={errorInStatus.error ? errorInStatus.errorMessage : ''} + onChange={handleChange} + > + {STATUS_OPTIONS.map((option, index) => ( + <MenuItem + key={option.value} + value={option.value} + style={option.value === status ? { color: 'blue' } : { color: 'black' }} + > + { + option.label + } + </MenuItem> + ))} + </TextField> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </> + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Adicionar' + } + </Button> + </CardAction> + </Card> + ); + } else return <Unauthorized/> +} + +export default CreateQuestion; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/CreateRating.js b/src/Admin/Components/Components/Inputs/CreateRating.js new file mode 100644 index 0000000000000000000000000000000000000000..290bea818d6309c2b887dfab06fbfd3bf71ad006 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateRating.js @@ -0,0 +1,248 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useContext } from 'react'; +//imports material ui componets +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +//imports local files +import { apiUrl } from '../../../../env'; +import { Store } from '../../../../Store'; +import SnackBar from '../../../../Components/SnackbarComponent'; +//imports services +import { Create } from '../../../Services'; +//router +import {Link} from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const CreateRating = (props) => { + const { state, dispatch } = useContext(Store); + + const [name, setName] = useState('Novo rating'); + const [description, setDescription] = useState(''); + + const [isLoading, setIsLoading] = useState(false) + + // Handle error in name + const [errorInName, setErrorInName] = useState({ + error: false, + message: '', + }) + + // Handle error in Country + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + setName(e.target.value) + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + } + + const DescriptionHandler = (e) => { + if (errorInDescription.error) { + setErrorInDescription({ + error: false, + message: '' + }) + } + setDescription(e.target.value) + } + + // verify if the given text is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + //Handle submit + async function onSubmit() { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(description)) { + const api = apiUrl + '/ratings' + const body = { + "rating": { + 'name': name, + 'description': description, + } + } + Create(api, body).then(res => { + if (res) { + HandleSnack('O rating foi criada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Esse campo está vazio' + }) + } + if (isEmpty(description)) { + setErrorInDescription({ + error: true, + message: 'Esse campo está vazio' + }) + } + setIsLoading(false) + } + } + + // Fields + const fields = [ + { + label: 'Nome', + value: name, + required: true, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event) + }, + { + label: 'Descrição', + value: description, + required: true, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event) + } + ] + + if(CheckUserPermission()) { + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' alignItems="center" alignContent="center" xs={12}> + <Grid item> + <Typography variant='h4'> + {name} + </Typography> + </Grid> + <Grid item> + <Link to={'/admin/Ratings'} style={{textDecoration: 'none'}}> + <Button + onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Adicionar' + } + </Button> + </CardAction> + </Card> + ); + } else return <Unauthorized/> +} + +export default CreateRating; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/EditCollection.js b/src/Admin/Components/Components/Inputs/EditCollection.js new file mode 100644 index 0000000000000000000000000000000000000000..6065d23ef6aef739a30968456514ee911ca54644 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditCollection.js @@ -0,0 +1,348 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useRef, useContext } from 'react'; +//imports material ui components +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +import SaveIcon from '@material-ui/icons/Save'; +import MenuItem from "@material-ui/core/MenuItem"; +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent'; +import Unauthorized from '../Unauthorized'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { Edit, GetSpecificData } from '../../../Services'; +import { EditFilter, GetAData } from '../../../Filters'; +//routers +import { Link } from 'react-router-dom'; +//joadit +import JoditEditor from 'jodit-react'; + +const EditCollection = ({ match }) => { + const { state, dispatch } = useContext(Store); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + + const [isLoading, setIsLoading] = useState(false); + const id = match.params.id + const [name, setName] = useState('') + const [privacy, setPrivacy] = useState('') + const [description, setDescription] = useState(''); + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '' + }) + + const [errorInPrivacy, setErrorInPrivacy] = useState({ + error: false, + message: '' + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const editor = useRef(null); + + //Configs for jodit + const config = { + zIndex: 0, + readonly: false, + activeButtonsInReadOnly: ["source", "fullsize", "print", "about"], + toolbarButtonSize: "middle", + theme: "default", + saveModeInCookie: false, + spellcheck: true, + editorCssClass: false, + triggerChangeEvent: true, + height: 400, + direction: "ltr", + debugLanguage: false, + tabIndex: -1, + toolbar: true, + enter: "P", + useSplitMode: false, + colorPickerDefaultTab: "background", + imageDefaultWidth: 100, + events: {}, + placeholder: "Digite a descrição...", + showXPathInStatusbar: false, + }; + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + + //Verify if the string is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Fields + const fields = [ + { + label: 'ID', + value: id, + default: true, + type: 'text' + }, + { + label: 'Nome', + value: name, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event), + default: false, + type: 'text', + }, + ] + + const privacyOptions = [ + { + value: "private", + label: "Privado", + }, + { + value: "public", + label: "Público", + }, + ]; + + //handle change of privacy + const handleChange = (e) => { + const value = e.target.value; + setPrivacy(value); + console.log(privacy) + }; + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + const onSubmit = async () => { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(privacy)) { + const api = EditFilter('collections', id) + const body = { + "collection": { + 'name': name, + 'privacy': privacy, + 'description': description + } + } + Edit(api, body).then(res => { + if (res) { + HandleSnack('A Coleção foi alterada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } + else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + if (isEmpty(privacy)) { + setErrorInPrivacy({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + setIsLoading(false) + } + } + + useEffect(() => { + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetSpecificData(GetAData("collections", match.params.id), headers).then( + (res) => { + if (res.state) { + setIsLoaded(true); + setError(false); + setName(res.data.name) + setPrivacy(res.data.privacy) + setDescription(res.data.description) + } else { + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div> Houve um erro... </div> + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if(CheckUserPermission()){ + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' xs={12} alignItems="center"> + <Grid item> + <Typography variant='h4'> + {name} + </Typography> + </Grid> + <Grid item> + <Link style={{ textDecoration: 'none' }} to={'/admin/Collections'}> + <Button + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + disabled={field.default} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{marginBottom : '1em'}} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + <TextField + select + label="Privacidade" + value={privacy ? privacy : ""} + error={errorInPrivacy.error} + helperText={errorInPrivacy.error ? errorInPrivacy.errorMessage : ''} + style={{ marginBottom: '1em' }} + onChange={handleChange} + > + {privacyOptions.map((option, index) => ( + <MenuItem + key={option.value} + value={option.value} + style={option.value === privacy ? {color : 'blue'} : {color : 'black'}} + > + { + option.value + } + </MenuItem> + ))} + </TextField> + <JoditEditor + ref={editor} + value={description} + config={config} + tabIndex={1} // tabIndex of textarea + onBlur={(newContent) => setDescription(newContent.target.innerHTML)} // preferred to use only this option to update the content for performance reasons + onChange={(newContent) => { }} + /> + </form> + </CardContent> + + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <SaveIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Salvar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized/> +} + +export default EditCollection; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/EditEducationalObect.js b/src/Admin/Components/Components/Inputs/EditEducationalObect.js new file mode 100644 index 0000000000000000000000000000000000000000..0ebb0b71b1926b27907fdc7635b059560b102623 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditEducationalObect.js @@ -0,0 +1,735 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useRef, useContext } from "react"; +import PropTypes from "prop-types"; +import SwipeableViews from "react-swipeable-views"; +import moment from 'moment'; +//imports material ui components +import { Typography, TextField, Button, Grid } from "@material-ui/core"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from "@material-ui/core/CardActions"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import { useTheme, makeStyles } from "@material-ui/core/styles"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import Box from "@material-ui/core/Box"; +import SaveIcon from "@material-ui/icons/Save"; +import LanguageRoundedIcon from "@material-ui/icons/LanguageRounded"; +import MenuBookRoundedIcon from '@material-ui/icons/MenuBookRounded'; +//imports local files +import SnackBar from "../../../../Components/SnackbarComponent"; +import { Store } from '../../../../Store'; +import Unauthorized from '../Unauthorized'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { Edit, GetFullList, GetSpecificData} from "../../../Services"; +import { EditFilter, GetAData, Url } from "../../../Filters"; +//routers +import { Link } from "react-router-dom"; +//jodit +import JoditEditor from "jodit-react"; + +let currLanPage = 0; +let currLanFilter = ""; + +let currObjTypePage = 0; +let currObjTypeFilter = ""; + +const useStyles = makeStyles((theme) => ({ + root: { + width: "100%", + maxWidth: 250, + backgroundColor: theme.palette.background.paper, + position: "relative", + overflow: "auto", + maxHeight: 300, + }, +})); + +const EditEducationalObject = ({ match }) => { + const { state, dispatch } = useContext(Store); + + const theme = useTheme(); + const classes = useStyles(); + + //Configs for jodit + const JODIT_CONFIG = { + zIndex: 0, + readonly: false, + activeButtonsInReadOnly: ["source", "fullsize", "print", "about"], + toolbarButtonSize: "middle", + theme: "default", + saveModeInCookie: false, + spellcheck: true, + editorCssClass: false, + triggerChangeEvent: true, + height: 400, + direction: "ltr", + debugLanguage: false, + tabIndex: -1, + toolbar: true, + enter: "P", + useSplitMode: false, + colorPickerDefaultTab: "background", + imageDefaultWidth: 100, + events: {}, + placeholder: "Digite a descrição...", + showXPathInStatusbar: false, + }; + + const id = match.params.id; + + const editor = useRef(null); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [isLoading, setIsLoading] = useState(false); //is loading to submit + + const [value, setValue] = React.useState(0); + + //mutables + const [name, setName] = useState(); + const [owner, setOwner] = useState(); + const [author, setAuthor] = useState(); + const [listOfLanguages, setListOfLanguages] = useState([]); + const [languagesID, setLanguagesID] = useState([]); + const [listOfObjectTypes, setListOfObjectTypes] = useState([]); + const [objectTypeName, setObjectTypeName] = useState(); + const [objectTypeID, setObjectTypeID] = useState(); + const [description, setDescription] = useState(""); + + // Imutables + const [CREATE_AT, setCreateAt] = useState(""); + const [UPDATE_AT, setUpdateAt] = useState(""); + const [VIEWS_COUNT, setViewsCount] = useState(""); + const [LIKES_COUNT, setLikesCount] = useState(""); + const [REVIEW_AVERAGE, setReviewAverage] = useState(""); + const [SCORE, setScore] = useState(""); + + const [errorInName, setErrorInName] = useState({ + error: false, + message: "", + }); + + const [errorInOwner, setErrorInOwner] = useState({ + error: false, + message: "", + }); + + const [errorInAuthor, setErrorInAuthor] = useState({ + error: false, + message: "", + }); + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: "", + }); + } + setName(e.target.value); + }; + + const OwnerHandler = (e) => { + if (errorInOwner.error) { + setErrorInOwner({ + error: false, + message: "", + }); + } + setOwner(e.target.value); + }; + + const AuthorHandler = (e) => { + if (errorInAuthor.error) { + setErrorInAuthor({ + error: false, + message: "", + }); + } + setAuthor(e.target.value); + }; + + const onChangeObjectHandler = async (e) => { + currObjTypePage = 0; + currObjTypeFilter = e.target.value; + GetFullList( + Url( + "object_types", + `"name" : "${currObjTypeFilter}"`, + currObjTypePage, + "DESC" + ) + ).then((res) => { + if (res.state) { + if(res.data.length === 0) HandleSnack("Não há nenhum tipo de objeto educacional com esse nome", true, "warning", "#FFC125"); + setListOfObjectTypes([...res.data]); + } else { + HandleSnack("Erro ao pegar os dados", true, "warning", "#FA8072"); + } + }); + }; + + const OnChangeLanguageHandler = async (e) => { + currLanPage = 0; + currLanFilter = e.target.value; + GetFullList( + Url("languages", `"name" : "${currLanFilter}"`, currLanPage, "DESC") + ).then((res) => { + if (res.state) { + if(res.data.length === 0) HandleSnack("Não há nenhuma linguagem com esse nome", true, "warning", "#FFC125"); + setListOfLanguages([...res.data]); + } else { + HandleSnack("Erro ao pegar os dados", true, "warning", "#FA8072"); + } + }); + }; + + const HandleLanguageScroll = async (e) => { + const bottom = + e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight; + if (bottom) { + currLanPage++; + GetFullList( + Url("languages", `"name" : "${currLanFilter}"`, currLanPage, "DESC") + ).then((res) => { + if (res.state) { + if (res.data.length >= 1) { + const currData = listOfLanguages.concat(res.data); + setListOfLanguages(currData); + } else { + HandleSnack( + "Não há mais linguagens para serem carregadas", + true, + "warning", + "#FFC125" + ); + } + } else { + HandleSnack( + "Erro ao carregar as linguagens", + true, + "warning", + "#FA8072" + ); + } + }); + } + }; + + const HandleObjectTypeScroll = async (e) => { + const bottom = + e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight; + if (bottom) { + currObjTypePage++; + GetFullList( + Url("languages", `"name" : "${currObjTypeFilter}"`, currObjTypePage, "DESC") + ).then((res) => { + if (res.state) { + if (res.data.length >= 1) { + const currData = listOfLanguages.concat(res.data); + setListOfObjectTypes(currData); + } else { + HandleSnack( + "Não há mais objetos educacionais para serem carregadas", + true, + "warning", + "#FFC125" + ); + } + } else { + HandleSnack( + "Erro ao carregar os objetos educacionais", + true, + "warning", + "#FA8072" + ); + } + }); + } + }; + + + const HandleLanguagePressed = (id) => { + const currSelectedLanguages = [...languagesID]; + + if (currSelectedLanguages[id] === id) currSelectedLanguages[id] = undefined; + else currSelectedLanguages[id] = id; + + setLanguagesID(currSelectedLanguages); + }; + + const HandleObjectTypePressed = (id, name) => { + setObjectTypeID(id); + setObjectTypeName(name); + }; + + //Verify if the string is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + }; + + function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + <div + role="tabpanel" + hidden={value !== index} + id={`full-width-tabpanel-${index}`} + aria-labelledby={`full-width-tab-${index}`} + {...other} + > + {value === index && <Box style={{ marginTop: "2em" }}>{children}</Box>} + </div> + ); + } + + TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.any.isRequired, + value: PropTypes.any.isRequired, + }; + + function a11yProps(index) { + return { + id: `full-width-tab-${index}`, + "aria-controls": `full-width-tabpanel-${index}`, + }; + } + + const handleChange = (event, newValue) => { + setValue(newValue); + }; + + const handleChangeIndex = (index) => { + setValue(index); + }; + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + const SaveData = (data) => { + const languages = []; + + for (let i = 0; i < data.language.length; i++) { + languages[data.language[i].id] = data.language[i].id; + } + setObjectTypeName(data.object_type); + setName(data.name); + setOwner(data.publisher.name); + setAuthor(data.author); + setReviewAverage(data.review_average); + setLikesCount(data.likes_count); + setViewsCount(data.views_count); + setScore(data.score); + setCreateAt(DisplayDate(data.created_at)); + setUpdateAt(DisplayDate(data.updated_at)); + setDescription(data.description); + setLanguagesID(languages); + }; + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + + //handle submit + const onSubmit = async () => { + console.log(languagesID); + setIsLoading(true); + if ( + !isEmpty(name) && + !isEmpty(owner) && + !isEmpty(author) && + !isEmpty(languagesID)&& + !isEmpty(objectTypeID) + ) { + const api = EditFilter("learning_objects", id); + const body = { + learning_object: { + author: author, + name: name, + description: description, + object_type_id: objectTypeID, + language_ids: languagesID, + }, + }; + Edit(api, body).then((res) => { + if (res) { + HandleSnack( + "O objeto educacional foi alterada com sucesso", + true, + "success", + "#228B22" + ); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + setIsLoading(false); + }); + } else { + HandleSnack( + "Você precisa preencher algumas informações obrigatórias", + true, + "warning", + "#FFC125" + ); + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: "Este campo precisa ser preenchido", + }); + } + setIsLoading(false); + } + }; + + // Fields + const fields = [ + { + label: "ID", + value: id, + default: true, + type: "text", + }, + { + label: "Nome", + value: name, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event), + default: false, + required: true, + type: "text", + }, + { + label: "Dono(a)", + value: owner, + error: errorInOwner.error, + errorMessage: errorInOwner.message, + onChange: (event) => OwnerHandler(event), + default: false, + required: true, + type: "text", + }, + { + label: "Autor(a)", + value: author, + error: errorInAuthor.error, + errorMessage: errorInAuthor.message, + onChange: (event) => AuthorHandler(event), + default: false, + required: true, + type: "text", + }, + ]; + + const Imutables = [ + { + label: "Score", + value: SCORE, + default: true, + type: "text", + }, + { + label: "Criado em", + value: CREATE_AT, + default: true, + type: "text", + }, + { + label: "Atualizado em", + value: UPDATE_AT, + default: true, + type: "text", + }, + { + label: "View count", + value: VIEWS_COUNT, + default: true, + type: "text", + }, + { + label: "Likes count", + value: LIKES_COUNT, + default: true, + type: "text", + }, + { + label: "Review average", + value: REVIEW_AVERAGE, + default: true, + type: "text", + }, + ]; + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + useEffect(() => { + GetSpecificData(GetAData("learning_objects", match.params.id)) + .then((res) => { + if(res.state){ + SaveData(res.data); + setIsLoaded(true); + } + else{ + setError(true); + } + }); + }, []); + + if (error) { + return <div> Houve um erro... </div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if(CheckUserPermission()){ + return ( + <Card variant="outlined"> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + <CardContent> + <Grid + container + direction="row" + justify="space-between" + xs={12} + alignContent="center" + alignItems="center" + > + <Grid item> + <Typography variant="h4">{name}</Typography> + </Grid> + <Grid item> + <Link + style={{ textDecoration: "none" }} + to={"/admin/learningObjects"} + > + <Button + startIcon={<ListRoundedIcon />} + variant="outlined" + color="primary" + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <Tabs + value={value} + onChange={handleChange} + indicatorColor="primary" + textColor="primary" + variant="fullWidth" + aria-label="full width tabs example" + > + <Tab label="Resumo" {...a11yProps(0)} /> + <Tab label="Descrição" {...a11yProps(1)} /> + <Tab label="Imutáveis" {...a11yProps(2)} /> + </Tabs> + + <SwipeableViews + axis={theme.direction === "rtl" ? "x-reverse" : "x"} + index={value} + onChangeIndex={handleChangeIndex} + > + <TabPanel value={value} index={0} dir={theme.direction}> + <form style={{ display: "flex", flexDirection: "column" }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + disabled={field.default} + error={field.error} + helperText={field.error ? field.errorMessage : ""} + style={{ width: "250px", marginBottom: "1em" }} + label={field.label} + defaultValue={field.value} + onBlur={field.onChange} + type="search" + multiline + /> + ))} + + <> + <TextField + style={{ width: "250px", marginBottom: "1em" }} + label="Digite o tipo de objeto" + onBlurCapture={onChangeObjectHandler} + type="search" + multiline + /> + {listOfObjectTypes.length >= 1 ? ( + <List + className={classes.root} + onScroll={HandleObjectTypeScroll} + > + {listOfObjectTypes.map((item, index) => ( + <ListItem key={item.id} divider> + <Button + variant="text" + startIcon={<MenuBookRoundedIcon />} + onClick={() => HandleObjectTypePressed(item.id, item.name)} + color={ + objectTypeName === item.name + ? "primary" + : "default" + } + > + {item.name} + </Button> + </ListItem> + ))} + </List> + ) : null} + </> + + <> + <TextField + style={{ width: "250px", marginBottom: "1em" }} + label="Digite as lÃnguas" + onBlurCapture={OnChangeLanguageHandler} + type="search" + multiline + /> + {listOfLanguages.length >= 1 ? ( + <List + className={classes.root} + onScroll={HandleLanguageScroll} + > + {listOfLanguages.map((item, index) => ( + <ListItem key={item.id} divider> + <Button + variant="text" + startIcon={<LanguageRoundedIcon />} + onClick={() => HandleLanguagePressed(item.id)} + color={ + languagesID[item.id] === item.id + ? "primary" + : "default" + } + > + {item.name} + </Button> + </ListItem> + ))} + </List> + ) : null} + </> + </form> + </TabPanel> + + <TabPanel value={value} index={1} dir={theme.direction}> + <JoditEditor + ref={editor} + value={description} + config={JODIT_CONFIG} + tabIndex={1} // tabIndex of textarea + onBlur={(newContent) => + setDescription(newContent.target.innerHTML) + } // preferred to use only this option to update the content for performance reasons + onChange={(newContent) => { }} + /> + </TabPanel> + + <TabPanel value={value} index={2} dir={theme.direction}> + <form style={{ display: "flex", flexDirection: "column" }}> + {Imutables.map((field, index) => ( + <TextField + key={index} + disabled={field.default} + style={{ width: "250px", marginBottom: "1em" }} + label={field.label} + defaultValue={field.value} + /> + ))} + </form> + </TabPanel> + </SwipeableViews> + </CardContent> + + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <SaveIcon />} + > + {isLoading ? <CircularProgress size={24} /> : "Salvar"} + </Button> + </CardAction> + </Card> + ); + } else return <Unauthorized/> +}; + +export default EditEducationalObject; diff --git a/src/Admin/Components/Components/Inputs/EditLanguage.js b/src/Admin/Components/Components/Inputs/EditLanguage.js new file mode 100644 index 0000000000000000000000000000000000000000..f3e778b2f0f69278ece1a505c217d1114dfad33b --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditLanguage.js @@ -0,0 +1,276 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useContext } from 'react'; +//imports material ui components +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +import SaveIcon from '@material-ui/icons/Save'; +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { Edit } from '../../../Services'; +import { EditFilter, GetAData } from '../../../Filters'; +//routers +import {Link} from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const EditLanguage = ({ match }) => { + const { state, dispatch } = useContext(Store); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + + const [isLoading, setIsLoading] = useState(false); + const id = match.params.id + const [name, setName] = useState() + const [code, setCode] = useState() + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '' + }) + + const [errorInCode, setErrorInCode] = useState({ + error: false, + message: '' + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + + const CodeHandler = (e) => { + if (errorInCode.error) { + setErrorInCode({ + error: false, + message: '' + }) + } + setCode(e.target.value) + } + + + //Verify if the string is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Fields + const fields = [ + { + label: 'ID', + value: id, + default: true, + type: 'text' + }, + { + label: 'Nome', + value: name, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event), + default: false, + type: 'text', + }, + { + label: 'Código', + value: code, + error: errorInCode.error, + errorMessage: errorInCode.message, + onChange: (event) => CodeHandler(event), + default: false, + type: 'text' + }, + ] + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + const onSubmit = async () => { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(code)) { + const api = EditFilter('languages', id) + const body = { + "language": { + 'name': name, + 'code': code, + } + } + Edit(api, body).then(res => { + if (res) { + HandleSnack('A linguagem foi alterada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } + else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + if (isEmpty(code)) { + setErrorInCode({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + setIsLoading(false) + } + } + + useEffect(() => { + fetch(GetAData("languages", match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setName(result.name) + setCode(result.code) + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div> Houve um erro... </div> + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if(CheckUserPermission()){ + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' alignContent="center" alignItems="center"> + <Typography variant='h4'> + {name} + </Typography> + <Link style={{textDecoration: 'none'}} to={'/admin/languages'}> + <Button + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + disabled={field.default} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <SaveIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Salvar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized/> +} + +export default EditLanguage; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/EditRating.js b/src/Admin/Components/Components/Inputs/EditRating.js new file mode 100644 index 0000000000000000000000000000000000000000..066ecaf6ada10c3d0e6a1626276499ede69d4a3a --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditRating.js @@ -0,0 +1,282 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useContext } from 'react'; +//imports material ui components +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +import SaveIcon from '@material-ui/icons/Save'; +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { Edit } from '../../../Services'; +import { EditFilter, GetAData } from '../../../Filters'; +//routers +import {Link} from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const EditRating = ({ match }) => { + const { state, dispatch } = useContext(Store); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + + const [isLoading, setIsLoading] = useState(false); + const id = match.params.id + const [name, setName] = useState() + const [description, setDescription] = useState() + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '' + }) + + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '' + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + + const DescriptionHandler = (e) => { + if (errorInDescription.error) { + setErrorInDescription({ + error: false, + message: '' + }) + } + setDescription(e.target.value) + } + + + //Verify if the string is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Fields + const fields = [ + { + label: 'ID', + value: id, + default: true, + type: 'text' + }, + { + label: 'Nome', + value: name, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event), + default: false, + required: true, + type: 'text', + }, + { + label: 'Descrição', + value: description, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event), + default: false, + required: true, + type: 'text' + }, + ] + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + const onSubmit = async () => { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(description)) { + const api = EditFilter('ratings', id) + const body = { + "rating": { + 'name': name, + 'description': description, + } + } + Edit(api, body).then(res => { + if (res) { + HandleSnack('O rating foi alterada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } + else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + if (isEmpty(description)) { + setErrorInDescription({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + setIsLoading(false) + } + } + + useEffect(() => { + fetch(GetAData("ratings", match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setName(result.name) + setDescription(result.description) + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div> Houve um erro... </div> + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if(CheckUserPermission()){ + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between' alignContent="center" alignItems="center" xs={12}> + <Grid item> + <Typography variant='h4'> + {name} + </Typography> + </Grid> + <Grid item> + <Link style={{textDecoration: 'none'}} to={'/admin/Ratings'}> + <Button + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + disabled={field.default} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <SaveIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Salvar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized/> +} + +export default EditRating; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/EmailInputs.js b/src/Admin/Components/Components/Inputs/EmailInputs.js new file mode 100644 index 0000000000000000000000000000000000000000..8faecf8d668d735b06ca2d1e1e58319b9a661704 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EmailInputs.js @@ -0,0 +1,521 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useRef } from "react"; +//material ui components +import TextField from "@material-ui/core/TextField"; +import MenuItem from "@material-ui/core/MenuItem"; +import FormGroup from "@material-ui/core/FormGroup"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Checkbox from "@material-ui/core/Checkbox"; +import SendRoundedIcon from "@material-ui/icons/SendRounded"; +import Button from "@material-ui/core/Button"; +import CloseRoundedIcon from "@material-ui/icons/CloseRounded"; +import Paper from "@material-ui/core/Paper"; +import IconButton from "@material-ui/core/IconButton"; +//imports from local files +import SnackBar from "../../../../Components/SnackbarComponent"; +import { SendEmail } from "../../../Services"; +import { apiUrl } from "../../../../env"; +//Jodit +import JoditEditor from "jodit-react"; + +let sendToAll = false; +let rolesArr = []; + +const EmailInputs = (props) => { + const [option, setOption] = useState("Todos os usuários"); //labels of the text field 'to' + const [index, setIndex] = useState(0); //Used to display something above the text field 'to' depending on what the user clicks + + // Capture th text insert by the user in the fields + const [emails, setEmails] = useState(props.email); + const [emailsAdress, setEmailsAdress] = useState([]); + const [subject, setSubject] = useState(""); + const [message, setMessage] = useState(""); + + const [isSending, setIsSending] = useState(false); + + const editor = useRef(null); + + //Configs for jodit + const config = { + zIndex: 0, + readonly: false, + activeButtonsInReadOnly: ["source", "fullsize", "print", "about"], + toolbarButtonSize: "middle", + theme: "default", + saveModeInCookie: false, + spellcheck: true, + editorCssClass: false, + triggerChangeEvent: true, + height: 400, + direction: "ltr", + debugLanguage: false, + tabIndex: -1, + toolbar: true, + enter: "P", + useSplitMode: false, + colorPickerDefaultTab: "background", + imageDefaultWidth: 100, + events: {}, + placeholder: "Digite sua mensagem...", + showXPathInStatusbar: false, + }; + + //Handle snack bar + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + //Controls the state of error in the textfields + const [errorInEmails, setErrorInEmail] = useState({ + error: false, + arroba: false, + message: "", + }); + const [errorInSubject, setErrorInSubject] = useState({ + error: false, + message: "", + }); + const [errorInMessage, setErrorInMessage] = useState({ + error: false, + message: "", + }); + + const options = [ + { + value: "All", + label: "Todos os usuários", + }, + { + value: "Roles", + label: "Roles/Permissões especÃficas", + }, + { + value: "Emails", + label: "1 ou mais emails", + }, + ]; + + //Roles of permissions + const [roles, setRoles] = useState([ + { + label: "Editor", + value: 7, + isChecked: false, + }, + { + label: "Admin", + value: 3, + isChecked: false, + }, + { + label: "Curador", + value: 4, + isChecked: false, + }, + { + label: "Professor", + value: 1, + isChecked: false, + }, + { + label: "Submetedor", + value: 8, + isChecked: false, + }, + { + label: "Aluno", + value: 2, + isChecked: false, + }, + { + label: "Moderador", + value: 5, + isChecked: false, + }, + { + label: "Parceiro", + value: 9, + isChecked: false, + }, + { + label: "Supervisor", + value: 6, + isChecked: false, + }, + { + label: "Publicador", + value: 10, + isChecked: false, + }, + ]); + + const handleChange = (e) => { + const value = e.target.value; + if (value === "All") { + sendToAll = true; + } else { + sendToAll = false; + } + setOption(value); + }; + + const handleChangeCheckBox = (i) => { + const currState = [...roles]; + currState[i].isChecked = !currState[i].isChecked; + setRoles(currState); + if (currState[i].isChecked) { + rolesArr[i] = currState[i].value; + } else { + rolesArr[i] = null; + } + }; + + const EmailsHandler = (e) => { + setEmails(e.target.value); + if (emails.length <= 1) { + const obj = { ...errorInEmails }; + obj.error = true; + obj.message = "* O email é pequeno"; + setErrorInEmail(obj); + } else { + const obj = { ...errorInEmails }; + obj.error = false; + obj.message = ""; + setErrorInEmail(obj); + const tam = emails.length; + if (emails[tam - 1] !== "@") { + const obj = { ...errorInEmails }; + if (!obj.arroba) { + obj.error = true; + obj.message = + "* Está faltando o caracter @ para ser identificado como um email válido"; + setErrorInEmail(obj); + } + } else { + const obj = { ...errorInEmails }; + obj.arroba = true; + obj.error = false; + obj.message = ""; + setErrorInEmail(obj); + } + } + }; + + const SubjectHandler = (e) => { + if (errorInSubject.error) { + setErrorInSubject({ + error: false, + message: "", + }); + } + setSubject(e.target.value); + }; + + const OnKeyPressHandler = (key) => { + if (key === 13) { + if (!isEmpty(emails)) { + if (emails.includes("@")) { + const arr = [...emailsAdress]; + arr.push(emails); + setEmails(""); + setEmailsAdress(arr); + setErrorInEmail({ + error: false, + message: "", + arroba: false, + }); + } else { + setErrorInEmail({ + error: true, + message: "Esse email não contém o caractere @", + arroba: false, + }); + } + } else { + setErrorInEmail({ + error: true, + message: "Esse campo precisa ser preenchido", + arroba: false, + }); + } + } + }; + + //Delete emails adress from the list to be sent a message + const HandleDelete = (i) => { + const copyEmail = [...emailsAdress]; + copyEmail.splice(i, 1); + setEmailsAdress(copyEmail); + console.log(copyEmail); + }; + + const isEmpty = (text) => { + return text.length === 0 ? true : false; + }; + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + const CleanFields = () => { + setMessage(""); + setSubject(""); + setEmailsAdress([]); + }; + + const submitRequest = async () => { + setIsSending(true); + if ( + !isEmpty(message) && + !isEmpty(subject) && + (!isEmpty(emailsAdress) || !isEmpty(rolesArr) || sendToAll) + ) { + const api = `${apiUrl}/email`; + const body = { + email: { + all_users: sendToAll, + subject: subject, + body: message, + emails: emailsAdress, + roles: rolesArr, + }, + }; + SendEmail(api, body).then((res) => { + if (res) { + HandleSnack( + "O email foi enviado com sucesso", + true, + "success", + "#228B22" + ); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + setIsSending(false); + CleanFields(); + }); + } else { + HandleSnack( + "Você precisa preencher algumas informações obrigatórias", + true, + "warning", + "#FFC125" + ); + if (isEmpty(message)) { + setErrorInMessage({ + error: true, + message: "Esse campo precisa ser preenchido", + }); + } + if (isEmpty(subject)) { + setErrorInSubject({ + error: true, + message: "Esse campo precisa ser preenchido", + }); + } + setIsSending(false); + CleanFields(); + } + }; + + return ( + <div + style={{ + width: "100%", + display: "flex", + flexDirection: "column", + justifyContent: "space-around", + }} + > + <form + style={{ + flex: 1, + display: "flex", + flexDirection: "column", + }} + > + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + + <TextField + id="outlined-input" + label="De *" + defaultValue="integrada.contato@mec.gov.br" + variant="outlined" + disabled={true} + /> + <div style={{ height: "1em" }} /> + + <TextField + select + label="Para *" + value={option ? option : ""} + onChange={handleChange} + helperText="Por favor, selecione uma das opções" + > + {options.map((option, index) => ( + <MenuItem + onClick={() => setIndex(index)} + key={option.value} + value={option.value} + > + {option.label} + </MenuItem> + ))} + </TextField> + <div style={{ height: "1em" }} /> + + {index === 0 ? null : index === 1 ? ( + <FormGroup style={{ marginBottom: "1em" }}> + {roles.map((role, index) => ( + <FormControlLabel + key={index} + control={ + <Checkbox + checked={role.isChecked} + onChange={() => handleChangeCheckBox(index)} + name={role.label} + color="primary" + /> + } + label={role.label} + /> + ))} + </FormGroup> + ) : ( + <> + <div + style={{ + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "Space-between", + }} + > + {emailsAdress.map((email, index) => ( + <Paper + elevation={3} + key={index} + style={{ + borderRadius: 28, + paddingLeft: "0.5em", + marginBottom: "1em", + marginRight: "1em", + backgroundColor: "#D3D3D3", + fontSize: 14, + display: "flex", + flexDirection: "row", + alignItems: "center", + }} + > + {email} + <IconButton onClick={() => HandleDelete(index)}> + <CloseRoundedIcon color="primary" /> + </IconButton> + </Paper> + ))} + </div> + + <TextField + id="outlined-input" + label="Emails" + rows={1} + error={errorInEmails.error} + helperText={errorInEmails.message} + value={emails} + onKeyPress={(key) => OnKeyPressHandler(key.which)} + onChange={EmailsHandler} + // onBlur={ShowEmails} + placeholder="Digite um email por vez e pressione Enter" + variant="outlined" + style={{ marginBottom: "1em" }} + /> + </> + )} + + <TextField + id="outlined-input" + label="Assunto" + value={subject} + error={errorInSubject.error} + helperText={errorInSubject.message} + placeholder="Digite o assunto do email" + onChange={SubjectHandler} + variant="outlined" + /> + </form> + + <div style={{ height: "1em" }} /> + + <div style={{ flex: 1 }}> + <JoditEditor + ref={editor} + value={message} + config={config} + tabIndex={1} // tabIndex of textarea + onBlur={(newContent) => setMessage(newContent.target.innerHTML)} // preferred to use only this option to update the content for performance reasons + onChange={(newContent) => { }} + /> + </div> + + <div style={{fontSize : 14}}> + * Se você deseja enviar foto da sua máquina, é preciso <a href="mailto:name@email.com">Clicar aqui</a> + </div> + + <div style={{ marginTop: 18 }}> + <Button + onClick={() => { + submitRequest(); + }} + variant="contained" + disabled={isSending} + color="primary" + startIcon={<SendRoundedIcon />} + > + {isSending ? <CircularProgress /> : "Enviar"} + </Button> + </div> + </div> + ); +}; + +export default EmailInputs; diff --git a/src/Admin/Components/Components/Inputs/IntitutionsInputs.js b/src/Admin/Components/Components/Inputs/IntitutionsInputs.js new file mode 100644 index 0000000000000000000000000000000000000000..11c2194e711f36d29d8516fccc8a7bfc9b5afb36 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/IntitutionsInputs.js @@ -0,0 +1,325 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useContext } from 'react'; +//imports material ui components +import { TextField, Button } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import SaveIcon from '@material-ui/icons/Save'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import Grid from "@material-ui/core/Grid"; +//imports local files +import Unauthorized from '../Unauthorized'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { Edit } from '../../../Services'; +import { EditFilter, GetAData } from '../../../Filters'; +//Routers +import { Link } from 'react-router-dom'; +import { stat } from 'fs'; + +let id; + +const EditInstitution = ({ match }) => { + const { state, dispatch } = useContext(Store); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [isAuthorized, setIsAuthorized] = useState(); + + const [name, setName] = useState(); + const [description, setDescription] = useState(); + const [adress, setAdress] = useState(); + const [city, setCity] = useState(); + const [country, setCountry] = useState(); + + //Used to display circle progress indicator when user click to save + const [isLoading, setIsLoading] = useState(false); + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '', + }) + + const [errorInCountry, setErrorInCountry] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + const DescriptionHandler = (e) => { + setDescription(e.target.value) + } + const AdressHandler = (e) => { + setAdress(e.target.value) + } + const CityHandler = (e) => { + setCity(e.target.value) + } + const CountryHandler = (e) => { + if (errorInCountry.error) { + setErrorInCountry({ + error: false, + message: '' + }) + } + setCountry(e.target.value) + } + + // verify if the given text is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + //Handle submit + async function onSubmit() { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(country)) { + const api = EditFilter('institutions', id) + const body = { + "institution": { + 'name': name, + 'description': description, + 'address': adress, + 'city': city, + 'country': country, + } + } + console.log(api) + Edit(api, body).then(res => { + if (res) { + HandleSnack('A instituição foi alterada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + + } else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Esse campo está vazio' + }) + } + if (isEmpty(country)) { + setErrorInCountry({ + error: true, + message: 'Esse campo está vazio' + }) + } + setIsLoading(false) + } + } + + // Fields + const fields = [ + { + label: 'Nome', + value: name, + required: true, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event) + }, + { + label: 'Descrição', + value: description, + required: false, + + onChange: (event) => DescriptionHandler(event) + }, + { + label: 'Endereço', + value: adress, + required: false, + + onChange: (event) => AdressHandler(event) + }, + { + label: 'Cidade', + value: city, + required: false, + onChange: (event) => CityHandler(event) + }, + { + label: 'PaÃs', + value: country, + required: true, + error: errorInCountry.error, + errorMessage: errorInCountry.message, + onChange: (event) => CountryHandler(event) + } + ] + + useEffect(() => { + fetch(GetAData("institutions", match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setName(result.name) + setDescription(result.description) + setAdress(result.adress) + setCity(result.city) + setCountry(result.country) + id = result.id + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div> Erro... </div> + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if (CheckUserPermission()) { + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container xs={12} justify="space-between" alignItems="center" alignContent="center"> + <Grid item> + <Typography + variant='h4' + color="inherit" + gutterBottom + > + {name} + </Typography> + </Grid> + + <Grid item> + <Link style={{ textDecoration: 'none' }} to={`/admin/intitutions`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div> + <div> + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </form> + </div> + <div> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <SaveIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Salvar' + } + </Button> + </div> + </div> + + </CardContent> + </Card> + ); + } else { + return <Unauthorized /> + } +} + +export default EditInstitution; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/NoteVarInputs.js b/src/Admin/Components/Components/Inputs/NoteVarInputs.js new file mode 100644 index 0000000000000000000000000000000000000000..3692e546a9c3192d11fd2b59057f327506a91ee1 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/NoteVarInputs.js @@ -0,0 +1,318 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect, useContext } from 'react'; +//imports material ui components +import { TextField, Button } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import SaveIcon from '@material-ui/icons/Save'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import Grid from '@material-ui/core/Grid'; +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { Edit } from '../../../Services'; +import { EditFilter, GetAData } from '../../../Filters'; +//Routers +import { Link } from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const NoteVarInputs = ({ match }) => { + const { state, dispatch } = useContext(Store); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + + const [isLoading, setIsLoading] = useState(false); + const id = match.params.id + const [name, setName] = useState() + const [code, setCode] = useState() + const [weight, setWeight] = useState() + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '' + }) + + const [errorInCode, setErrorInCode] = useState({ + error: false, + message: '' + }) + + const [errorInWeight, setErrorInWeight] = useState({ + error: false, + message: '' + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + + const CodeHandler = (e) => { + if (errorInCode.error) { + setErrorInCode({ + error: false, + message: '' + }) + } + setCode(e.target.value) + } + + const WeightHandler = (e) => { + if (errorInWeight.error) { + setErrorInWeight({ + error: false, + message: '' + }) + } + setWeight(e.target.value) + } + + //Verify if the string is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Fields + const fields = [ + { + label: 'ID', + value: id, + default: true, + type: 'text' + }, + { + label: 'Nome', + value: name, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event), + default: false, + type: 'text', + }, + { + label: 'Código', + value: code, + error: errorInCode.error, + errorMessage: errorInCode.message, + onChange: (event) => CodeHandler(event), + default: false, + type: 'text' + }, + { + label: 'Peso', + value: weight, + error: errorInWeight.error, + errorMessage: errorInWeight.message, + onChange: (event) => WeightHandler(event), + default: false, + type: 'number', + } + ] + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const onSubmit = async () => { + setIsLoading(true) + if (!isEmpty(name) && !isEmpty(code) && !isEmpty(weight)) { + const api = EditFilter('scores', id) + const body = { + "score": { + 'name': name, + 'code': code, + 'weight': weight + } + } + Edit(api, body).then(res => { + if (res) { + HandleSnack('A variável de nota foi alterada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } + else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(name)) { + setErrorInName({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + if (isEmpty(code)) { + setErrorInCode({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + if (isEmpty(weight)) { + setErrorInWeight({ + error: true, + message: 'Este campo precisa ser preenchido' + }) + } + setIsLoading(false) + } + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + + useEffect(() => { + fetch(GetAData("scores", match.params.id)) + .then((res) => res.json()) + .then( + (result) => { + setIsLoaded(true); + setName(result.name) + setCode(result.code) + setWeight(result.weight) + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ); + }, []); + + if (error) { + return <div>Houve um erro...</div> + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else if(CheckUserPermission()){ + return ( + <Card> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + + <Grid container xs={12} justify="space-between" alignItems="center" alignContent="center"> + <Grid> + <Typography + variant='h4' + color="inherit" + gutterBottom + > + {name} + </Typography> + </Grid> + + <Grid> + <Link style={{ textDecoration: 'none' }} to={`/admin/noteVars`}> + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + error={field.error} + disabled={field.default} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type={field.type} + multiline={true} + /> + ))} + </form> + + <div> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <SaveIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Salvar' + } + </Button> + </div> + + </CardContent> + </Card> + ) + } else return <Unauthorized/> +} + +export default NoteVarInputs; \ No newline at end of file diff --git a/src/Admin/Components/Components/Table.js b/src/Admin/Components/Components/Table.js new file mode 100644 index 0000000000000000000000000000000000000000..2da01f137d05e54774ab9ee595070ca6b1003386 --- /dev/null +++ b/src/Admin/Components/Components/Table.js @@ -0,0 +1,80 @@ +import React from 'react'; +// material ui componets +import Table from '@material-ui/core/Table'; +import { withStyles, makeStyles } from '@material-ui/core/styles'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableContainer from '@material-ui/core/TableContainer'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import Paper from '@material-ui/core/Paper'; +import FilterListRoundedIcon from '@material-ui/icons/FilterListRounded'; +import { IconButton } from '@material-ui/core'; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const useStyles = makeStyles({ + table: { + minWidth: 700, + width : "100%" + }, + root: { + minWidth: 275, + boxShadow: '2px 2px 1px #A9A9A9' + }, + bullet: { + display: 'inline-block', + margin: '0 2px', + transform: 'scale(0.8)', + }, + title: { + fontSize: 28, + fontWeight: "500" + }, + pos: { + marginBottom: 12, + }, +}); + + +const TableData = (props) => { + const classes = useStyles(); + return ( + <TableContainer component={Paper}> + <Table className={classes.table} aria-label="customized table"> + <TableHead> + <TableRow> + { + props.top.map((top, index) => ( + index === 0 ? + <StyledTableCell key={index}> + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}> + {top} + { + props.onIconPressed === undefined ? <div></div> : <IconButton onClick={props.onIconPressed} color='primary'> + <FilterListRoundedIcon style={{ color: 'white' }} /> + </IconButton> + } + </div> + </StyledTableCell> + : + <StyledTableCell align="right" key={index}>{top}</StyledTableCell> + )) + } + </TableRow> + </TableHead> + {props.children} + </Table> + </TableContainer> + ); +} + +export default TableData; \ No newline at end of file diff --git a/src/Admin/Components/Components/Unauthorized.js b/src/Admin/Components/Components/Unauthorized.js new file mode 100644 index 0000000000000000000000000000000000000000..decfa6b5aa2504d3aedb0199faa97c66b84025d0 --- /dev/null +++ b/src/Admin/Components/Components/Unauthorized.js @@ -0,0 +1,41 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, {useState} from 'react'; +import SnackBar from '../../../Components/SnackbarComponent'; +import { Link } from 'react-router-dom'; +const Unauthorized = () => { + + const [openSnack, setOpenSnack] = useState(true); + + return ( + <div style={{ textAlign: 'center' }}> + <SnackBar + severity='warning' + text='Você não tem as permissões necessárias' + snackbarOpen={openSnack} + handleClose={() => setOpenSnack(false)} + /> + <Link to='/'> + Redirecionar para home + </Link> + </div> + ); +} + +export default Unauthorized; \ No newline at end of file diff --git a/src/Admin/Components/Components/Welcome.js b/src/Admin/Components/Components/Welcome.js new file mode 100644 index 0000000000000000000000000000000000000000..325fbe55aa2ff50392f12434c3c9528689149d44 --- /dev/null +++ b/src/Admin/Components/Components/Welcome.js @@ -0,0 +1,38 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React from 'react'; +import {Styles} from '../Styles/WelcomeStyle'; + +// This file is a component that says welcome to the user + +const Welcome = () => { + const classes = Styles(); + return( + <div style={classes.welcomeContainer}> + <p style={classes.welcomeTitle}> + Seja bem vindo ao portal do administrador + </p> + <p> + O site ainda está em desenvolvimento, qualquer indicação ou reclamação enviar ao email portalmec_tec@inf.ufpr.br . + </p> + </div> + ); +} + +export default Welcome; \ No newline at end of file diff --git a/src/Admin/Components/Styles/DataCard.js b/src/Admin/Components/Styles/DataCard.js new file mode 100644 index 0000000000000000000000000000000000000000..17083da50639dd57f55aaedbfd45f1f9a825735c --- /dev/null +++ b/src/Admin/Components/Styles/DataCard.js @@ -0,0 +1,56 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import { makeStyles } from "@material-ui/core/styles"; + +const useStyles = makeStyles({ + table: { + minWidth: 700, + }, + root: { + minWidth: 900, + boxShadow: "2px 2px 1px #A9A9A9", + }, + title: { + fontSize: 28, + fontWeight: "500", + }, + subTitle: { + fontSize: 14, + fontWeight: "500", + }, + pos: { + marginBottom: 12, + }, + displayRow: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + displayColumn: { + display: "flex", + flexDirection: "column", + marginBottom: "1em", + }, + marginTop : { + marginTop : '1em', + } +}); + +export { useStyles }; \ No newline at end of file diff --git a/src/Admin/Components/Styles/WelcomeStyle.js b/src/Admin/Components/Styles/WelcomeStyle.js new file mode 100644 index 0000000000000000000000000000000000000000..11364da864132b3ad83519ac3eaff9704ac81b7d --- /dev/null +++ b/src/Admin/Components/Styles/WelcomeStyle.js @@ -0,0 +1,33 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +const Styles = () => ({ + welcomeContainer : { + padding : '1em 1em', + borderRadius : '22px', + backgroundColor : 'white', + boxShadow : '2px 2px 1px #A9A9A9' + }, + welcomeTitle : { + fontWeight : 'bold', + fontSize : '20px', + } +}); + +export { Styles }; + diff --git a/src/Admin/Filters.js b/src/Admin/Filters.js new file mode 100644 index 0000000000000000000000000000000000000000..f519d245fbe74d09c1a2ba3e928c9d507763bea8 --- /dev/null +++ b/src/Admin/Filters.js @@ -0,0 +1,56 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import { apiUrl } from '../env'; + +export function Url(type, filter, page , sort) { + const api = `${apiUrl}/${type}?filter={${filter}}&page=${page}&range=[0,9]&results_per_page=10&sort=["id","${sort}"]` + return (api) +} + +export function GetOneOfAllUrl(type , filter){ + const api = `${apiUrl}/${type}?filter={${filter}}` + return (api) +} + +export function MethodsToComplain(type , id, meth){ + const api = `${apiUrl}/${type}/${id}/${meth}`; + return (api) +} + +export function GetAll(type){ + const api = `${apiUrl}/${type}` + return (api) +} + + +export function GetAData(type , id){ + const api = `${apiUrl}/${type}/${id}` + return (api) +} + + +export function EditFilter(type , id){ + const api = `${apiUrl}/${type}/${id}` + return (api) +} + +export function DeleteFilter(type , id){ + const api = `${apiUrl}/${type}/${id}` + return (api) +} \ No newline at end of file diff --git a/src/Admin/Pages/AdminLabelTabs/LabelTabs.js b/src/Admin/Pages/AdminLabelTabs/LabelTabs.js new file mode 100644 index 0000000000000000000000000000000000000000..8755b2883ee3149ac71708e7c96059af8f51bf61 --- /dev/null +++ b/src/Admin/Pages/AdminLabelTabs/LabelTabs.js @@ -0,0 +1,94 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +//This file has the labels of the left menu navigation from admin page + +const TabsItens = [ + { + label: "Home", + href: '/admin/home' + }, + { + label: 'Coleções', + href: '/admin/Collections', + }, + { + label: "Atividades", + href: '/admin/activities', + }, + { + label: "Dúvidas da comunidade", + href: '/admin/CommunityQuestions' + }, + { + label: "Instituições", + href: '/admin/intitutions' + }, + { + label: "Linguagens", + href: '/admin/languages', + }, + { + label: "Objetos educacionais", + href: "/admin/learningObjects", + }, + { + label: "Rating", + href : '/admin/Ratings' + }, + { + label: "Permissões do usuário", + }, + { + label: "Variáveis de nota", + href:'/admin/noteVars' + }, + { + label: "Perguntas curadoria", + href: "/admin/Questions" + }, + { + label: "Aprovação de professores", + }, + { + label: "Usuários", + }, + { + label: "Usuários bloqueados", + }, + { + label: "Denúncias", + href: "/admin/complaints", + }, + { + label: "Enviar email", + href:'/admin/sendEmail/none' + }, + { + label: "Métricas", + href:'/admin/inframe', + }, + { + label: "Configurações", + }, + { + label: "Sair", + }, +]; + +export { TabsItens }; diff --git a/src/Admin/Pages/Pages/Admin.js b/src/Admin/Pages/Pages/Admin.js new file mode 100644 index 0000000000000000000000000000000000000000..607dd85d750773468ee635ebe1b7e630f7001a14 --- /dev/null +++ b/src/Admin/Pages/Pages/Admin.js @@ -0,0 +1,200 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useContext } from 'react'; +//imports from material ui +import clsx from 'clsx'; +import { makeStyles } from '@material-ui/core/styles'; +import Drawer from '@material-ui/core/Drawer'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import IconButton from '@material-ui/core/IconButton'; +import DisplayIcon from '../../Components/Components/DisplayIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import MenuIcon from '@material-ui/icons/Menu'; +import Fab from '@material-ui/core/Fab'; +//imports from routers +import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'; +//imports from local files +import { TabsItens } from '../AdminLabelTabs/LabelTabs'; +import { Store } from '../../../Store'; +import Unauthorized from '../../Components/Components/Unauthorized'; +import Welcome from '../../Components/Components/Welcome'; +import NoteVariables from './SubPages/NoteVariables'; +import Institution from './SubPages/Institutions'; +import SendEmail from './SubPages/SendEmail'; +import Inframe from './SubPages/Inframe'; +import Languages from './SubPages/Languages'; +import Activity from './SubPages/Activity'; +import InstitutionCard from '../../Components/Components/DataCards/InstitutionsCard'; +import InstitutionsInput from '../../Components/Components/Inputs/IntitutionsInputs'; +import CreateInstitution from '../../Components/Components/Inputs/CreateInstitution'; +import NoteVarCard from '../../Components/Components/DataCards/NoteVarCard'; +import NoteVarInputs from '../../Components/Components/Inputs/NoteVarInputs'; +import EditLanguage from '../../Components/Components/Inputs/EditLanguage'; +import CreateLanguage from '../../Components/Components/Inputs/CreateLanguage'; + +import ActivityCard from '../../Components/Components/DataCards/ActivityCard'; + +import CommunityQuestions from './SubPages/CommunityQuestions'; +import CommunityCard from '../../Components/Components/DataCards/CommunityQuestionCard'; + +import Collections from './SubPages/Collections'; +import CollectionCard from '../../Components/Components/DataCards/CollectionCard'; +import EditCollection from '../../Components/Components/Inputs/EditCollection'; + +import Ratings from './SubPages/Rating'; +import RatingCard from '../../Components/Components/DataCards/RatingCard'; +import EditRating from '../../Components/Components/Inputs/EditRating'; +import CreateRating from '../../Components/Components/Inputs/CreateRating'; + +import Questions from './SubPages/Questions'; +import CreateQuestions from '../../Components/Components/Inputs/CreateQuestion'; + +import EducationalObject from './SubPages/EducationalObjects'; +import EducationalObjectCard from '../../Components/Components/DataCards/EducationalObjectsCard'; +import EducationalObjectEdit from '../../Components/Components/Inputs/EditEducationalObect'; + +import Complaints from './SubPages/Complaints'; +import ComplaintCard from '../../Components/Components/DataCards/ComplaintsCard'; +import { match } from 'assert'; + +const useStyles = makeStyles({ + list: { + width: 250, + }, + fullList: { + width: 'auto', + }, +}); + +const fab = { + margin: 0, + top: 'auto', + right: 20, + bottom: 20, + left: 'auto', + position: 'fixed', +} + +export default function Admin() { + const { state, dispatch } = useContext(Store); + const classes = useStyles(); + + //State of the Drawer + const [State, setState] = React.useState({ + left: false + }); + + const [IndexIcon, setIndexIcon] = useState(0); + + {/**************** Controlls the state of the Drawer ****************/ } + const toggleDrawer = (anchor, open) => (event) => { + if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) { + return; + } + + setState({ ...State, [anchor]: open }); + }; + + {/**************** Dsiplay the itens of the Drawer ****************/ } + const list = (anchor) => ( + <div + className={clsx(classes.list, { + [classes.fullList]: anchor === 'top' || anchor === 'bottom', + })} + role="presentation" + onClick={toggleDrawer(anchor, false)} + onKeyDown={toggleDrawer(anchor, false)} + > + <List> + {TabsItens.map((text, index) => ( + <Link to={text.href} key={text.label} style={{ color: "black" }}> + <ListItem button key={text.label} onClick={() => setIndexIcon(index)}> + <IconButton> + <DisplayIcon i={index} /> + </IconButton> + <ListItemText primary={text.label} /> + </ListItem> + </Link> + ))} + </List> + </div> + ); + + return ( + <BrowserRouter> + <Switch> + <div style={{ paddingTop: '2em', paddingLeft: '2em', paddingRight: '2em', paddingBottom: '2em', backgroundColor: ' #D3D3D3' }}> + <Route path='/admin/home' exact={true} component={Welcome}/> + + <Route path='/admin/intitutions' component={Institution} /> + <Route path='/admin/institution/:id' component={InstitutionCard} /> + <Route path='/admin/institutionEdit/:id' component={InstitutionsInput}/> + <Route path='/admin/InstitutionCreate' component={CreateInstitution} /> + + <Route path='/admin/noteVars' component={NoteVariables} /> + <Route path='/admin/noteVar/:id' component={NoteVarCard} /> + <Route path='/admin/noteVarEdit/:id' component={NoteVarInputs} /> + + <Route path='/admin/languages' component={Languages} /> + <Route path='/admin/languageEdit/:id' component={EditLanguage} /> + <Route path='/admin/languageCreate' component={CreateLanguage} /> + + <Route path='/admin/CommunityQuestions' component={CommunityQuestions} /> + <Route path='/admin/CommunityQuestion/:id' component={CommunityCard} /> + + <Route path='/admin/Collections' component={Collections} /> + <Route path='/admin/Collection/:id' component={CollectionCard} /> + <Route path='/admin/EditCollection/:id' component={EditCollection} /> + + <Route path='/admin/Ratings' component={Ratings} /> + <Route path='/admin/Rating/:id' component={RatingCard} /> + <Route path='/admin/EditRating/:id' component={EditRating} /> + <Route path='/admin/CreateRating' component={CreateRating} /> + + <Route path='/admin/Questions' component={Questions} /> + <Route path='/admin/CreateQuestion' component={CreateQuestions} /> + + <Route path='/admin/activities' component={Activity} /> + <Route path='/admin/activity/:id' component={ActivityCard} /> + + <Route path='/admin/learningObjects' component={EducationalObject} /> + <Route path='/admin/learningObject/:id' component={EducationalObjectCard} /> + <Route path='/admin/learningObjectEdit/:id' component={EducationalObjectEdit} /> + + <Route path='/admin/complaints' component={Complaints} /> + <Route path='/admin/complaint/:id' component={ComplaintCard} /> + + <Route path='/admin/sendEmail/:email' component={SendEmail} /> + <Route path='/admin/inframe' component={Inframe} /> + + </div> + </Switch> + + <React.Fragment> + <Drawer anchor={'left'} open={State['left']} onClose={toggleDrawer('left', false)}> + {list('left')} + </Drawer> + </React.Fragment> + <Fab color="primary" aria-label="add" style={fab} onClick={toggleDrawer('left', true)}> + <MenuIcon /> + </Fab> + </BrowserRouter> + ); +} diff --git a/src/Admin/Pages/Pages/SubPages/Activity.js b/src/Admin/Pages/Pages/SubPages/Activity.js new file mode 100644 index 0000000000000000000000000000000000000000..9f5b6581a36ac42d0a2ed6f4a2b6f917f888b8b1 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Activity.js @@ -0,0 +1,392 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState } from "react"; +import moment from 'moment'; +//imports from local files +import TableData from "../../../Components/Components/Table"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import { Url } from "../../../Filters"; +import { GetFullList } from "../../../Services"; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports from material ui +import { withStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import MenuItem from "@material-ui/core/MenuItem"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import IconButton from "@material-ui/core/IconButton"; +import { Button, Typography, Paper, Grid } from "@material-ui/core"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +//routers +import { Link } from 'react-router-dom'; + +let currPage = 0; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Activity = () => { + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = [ + "CRIADO EM", + "DONO(A)", + "ATIVIDADE", + "PRIVACIDADE", + "VISUALIZAR", + ]; //Labels from Table + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + + const [showFilter, setShowFilter] = useState(false); + + const [option, setOption] = useState("Todos os usuários"); //labels of the text field 'to' + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + const options = [ + { + value: "private", + label: "Privado", + }, + { + value: "public", + label: "Público", + }, + ]; + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(api, headers).then((res) => { + if (res.state) { + const arrData = [...res.data]; + if (arrData.length === 0) { + HandleSnack( + "Não há mais dados para serem carregados", + true, + "warning", + "#FFC125" + ); + } else { + const arrItems = [...items]; + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData); + setItems(arrResult.concat(ADD_ONE_LENGHT)); + } + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsLoadingMoreItems(false); + }); + }; + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(api, headers).then((res) => { + console.log(res); + if (res.state) { + HandleSnack( + "A lista de dados foi atualizada", + true, + "success", + "#228B22" + ); + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsUpdating(false); + }); + }; + + const handleChange = (e) => { + const value = e.target.value; + console.log(value); + currPage = 0; + setOption(value); + + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetFullList( + Url("activities", `"privacy" : "${value}"`, `${currPage}`, "DESC"), + headers + ).then((res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + HandleSnack("Filtro aplicado com sucesso", true, "success", "#228B22"); + setIsLoaded(true); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + } + }); + }; + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + //getting data from server + useEffect(() => { + GetFullList(Url("activities", "", `${currPage}`, "DESC")).then( + (res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + setIsLoaded(true); + setError(false); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else { + + return <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + + <Paper style={{ padding: "1em" }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4">Atividades</Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0; + UpdateHandler( + Url("activities", "", `${currPage}`, "DESC") + ); + }} + startIcon={<UpdateRoundedIcon />} + > + {isUpdating ? <CircularProgress size={24} /> : "Atualizar"} + </Button> + </Grid> + <Grid item> + <Button + variant="contained" + color="secondary" + onClick={() => { + currPage = 0; + UpdateHandler( + Url("activities", "", `${currPage}`, "DESC") + ); + setShowFilter(!showFilter); + }} + startIcon={<FilterListRoundedIcon />} + > + Filtrar + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + + {showFilter ? ( + <> + <div style={{ height: "1em" }}></div> + + <div style={{ alignSelf: "flex-end" }}> + <TextField + select + label="Filtro" + value={option ? option : ""} + onChange={handleChange} + helperText="Por favor, selecione uma das opções" + > + {options.map((option, index) => ( + <MenuItem + key={option.value} + value={option.value} + > + {option.label} + </MenuItem> + ))} + </TextField> + </div> + </> + ) : null} + </Paper> + + <div style={{ height: "2em" }}></div> + + <TableData top={TOP_LABELS}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index} style={{ padding: "1em" }}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color="primary" + variant="text" + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++; + if (showFilter) { + LoadMoreItens( + Url("activities", `"privacy" : "${option}"`, `${currPage}`, "DESC") + ); + } else { + LoadMoreItens( + Url("activities", "", `${currPage}`, "DESC") + ); + } + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + { + row.owner === null ? '' : row.owner.name + } + </StyledTableCell> + <StyledTableCell align="right"> + {row.activity} + </StyledTableCell> + <StyledTableCell align="right">{row.privacy}</StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/activity/${row.id}`}> + <IconButton> + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + </> + } +}; +export default Activity; diff --git a/src/Admin/Pages/Pages/SubPages/Collections.js b/src/Admin/Pages/Pages/SubPages/Collections.js new file mode 100644 index 0000000000000000000000000000000000000000..a55b4d491f5c6192e34ebe6c178fd3fc5be0a2d8 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Collections.js @@ -0,0 +1,516 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState } from "react"; +import moment from 'moment'; +//imports from local files +import TableData from "../../../Components/Components/Table"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import { Url, EditFilter } from "../../../Filters"; +import { GetFullList, Delete } from "../../../Services"; +import AlertDialog from "../../../Components/Components/AlertDialog"; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports from material ui +import { withStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import MenuItem from "@material-ui/core/MenuItem"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import IconButton from "@material-ui/core/IconButton"; +import { Button, Typography, Paper, Grid } from "@material-ui/core"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import DeleteIcon from "@material-ui/icons/Delete"; +//routers +import { Link } from 'react-router-dom'; + +let currPage = 0; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Collections = () => { + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = [ + "NOME", + "DESCRIÇÃO", + "DONO(A)", + "CRIAÇÃO", + "ATUALIZAÇÃO", + "PRIVACIDADE", + "VISUALIZAR", + "DELETAR" + ]; //Labels from Table + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + + const [showFilter, setShowFilter] = useState(false); + const [search, setSearch] = useState(''); + + const [openAlertDialog, setOpenAlertDialog] = useState(false); + const [deleteItem, setDeleteItem] = useState({}); //Delete Item + const [isLoadingToDelete, setIsLoadingToDelete] = useState(null); + + const [option, setOption] = useState("Todos os usuários"); //labels of the text field 'to' + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + const options = [ + { + value: "private", + label: "Privado", + }, + { + value: "public", + label: "Público", + }, + ]; + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(api, headers).then((res) => { + if (res.state) { + const arrData = [...res.data]; + if (arrData.length === 0) { + HandleSnack( + "Não há mais dados para serem carregados", + true, + "warning", + "#FFC125" + ); + } else { + const arrItems = [...items]; + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData); + setItems(arrResult.concat(ADD_ONE_LENGHT)); + } + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsLoadingMoreItems(false); + }); + }; + + //Defines which row must show the circular progress + const HandleStateCircularProgress = (i) => { + setIsLoadingToDelete(i); + }; + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(api, headers).then((res) => { + console.log(res); + if (res.state) { + HandleSnack( + "A lista de dados foi atualizada", + true, + "success", + "#228B22" + ); + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsUpdating(false); + }); + }; + + //Called when user want to delete one institution + async function DeleteHandler() { + const id = deleteItem.id; + HandleStateAlertDialog(null); + Delete(EditFilter("collections", id)).then((res) => { + if (res) { + HandleSnack( + "A instituição foi deletada com sucesso", + true, + "success", + "#228B22" + ); + currPage = 0; + UpdateHandler(Url("collections", "", `${currPage}`, "DESC")); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + HandleStateCircularProgress(null); + }); + } + + //Controlls the state of the Alert Dialog + const HandleStateAlertDialog = (i) => { + const obj = { ...items[i] }; + setDeleteItem(obj); + setOpenAlertDialog(!openAlertDialog); + }; + + + // handle change of privacy + const handleChange = (e) => { + const value = e.target.value; + console.log(value); + currPage = 0; + setOption(value); + + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetFullList( + Url("collections", `"privacy" : "${value}"`, `${currPage}`, "DESC"), + headers + ).then((res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + HandleSnack("Filtro aplicado com sucesso", true, "success", "#228B22"); + setIsLoaded(true); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + } + }); + }; + + //Handle the search filter + const HandleSearch = (event) => { + setSearch(event.target.value) + + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + }; + + GetFullList( + Url("collections", `"name" : "${search}"`, `${currPage}`, "DESC"), + headers + ).then((res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + setIsLoaded(true); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + } + }); + } + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + //getting data from server + useEffect(() => { + GetFullList(Url("collections", "", `${currPage}`, "DESC")).then( + (res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + setIsLoaded(true); + setError(false); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(false); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else { + + return <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + + <Paper style={{ padding: "1em" }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4">Coleções</Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0; + UpdateHandler( + Url("collections", "", `${currPage}`, "DESC") + ); + }} + startIcon={<UpdateRoundedIcon />} + > + {isUpdating ? <CircularProgress size={24} /> : "Atualizar"} + </Button> + </Grid> + <Grid item> + <Button + variant="contained" + color="secondary" + onClick={() => { + currPage = 0; + UpdateHandler( + Url("collections", "", `${currPage}`, "DESC") + ); + setShowFilter(!showFilter); + }} + startIcon={<FilterListRoundedIcon />} + > + Filtrar + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + + {showFilter ? ( + <> + <div style={{ height: "1em" }}></div> + + <Grid container alignItems="center" alignContent="center" xs={12} direction="row" justify="space-between"> + <Grid item> + <TextField + select + label="Filtro" + value={option ? option : ""} + onChange={handleChange} + helperText="Por favor, selecione uma das opções" + > + {options.map((option, index) => ( + <MenuItem + key={index} + value={option.value} + > + {option.label} + </MenuItem> + ))} + </TextField> + </Grid> + + <Grid item> + <TextField + label="Pesquisa" + onChange={(event) => HandleSearch(event)} + > + + </TextField> + </Grid> + </Grid> + </> + ) : null} + </Paper> + + <div style={{ height: "2em" }}></div> + + <TableData top={TOP_LABELS}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index}> + <StyledTableCell> + <Button + key={index} + color="primary" + variant="text" + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++; + if (showFilter) { + if (search) { + LoadMoreItens( + Url("collections", `"name" : "${search}"`, `${currPage}`, "DESC") + ); + } + else { + LoadMoreItens( + Url("collections", `"privacy" : "${option}"`, `${currPage}`, "DESC") + ); + } + } else { + LoadMoreItens( + Url("collections", "", `${currPage}`, "DESC") + ); + } + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row"> + {row.name} + </StyledTableCell> + <StyledTableCell align="right"> + <div dangerouslySetInnerHTML={{ __html: row.description }}> + </div> + </StyledTableCell> + <StyledTableCell align="right"> + { + row.owner === null ? '' : row.owner.name + } + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.updated_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {row.privacy} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/Collection/${row.id}`}> + <IconButton> + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + <StyledTableCell align="right"> + {isLoadingToDelete === index ? ( + <CircularProgress size={24} color="primary" /> + ) : ( + <IconButton + onClick={() => { + HandleStateAlertDialog(index); + HandleStateCircularProgress(index); + }} + > + <DeleteIcon style={{ fill: "#FF0000" }} /> + </IconButton> + )} + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + + {/* This alert will be displayed if the user click to delete an institution */} + <AlertDialog + open={openAlertDialog} + OnDelete={DeleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpenAlertDialog(false); + HandleStateCircularProgress(null); + }} + /> + </> + } +}; +export default Collections; diff --git a/src/Admin/Pages/Pages/SubPages/CommunityQuestions.js b/src/Admin/Pages/Pages/SubPages/CommunityQuestions.js new file mode 100644 index 0000000000000000000000000000000000000000..a0d161cf600e95a9c138fa131aa3acdd5e41dc74 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/CommunityQuestions.js @@ -0,0 +1,569 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState, useContext } from "react"; +import moment from 'moment'; +//imports from local files +import TableData from "../../../Components/Components/Table"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import Unauthorized from '../../../Components/Components/Unauthorized'; +import { Url } from "../../../Filters"; +import { GetFullList } from "../../../Services"; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports from material ui +import { withStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import Popover from "@material-ui/core/Popover"; +import IconButton from "@material-ui/core/IconButton"; +import { Button, Typography, Paper, Grid } from "@material-ui/core"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import CancelRoundedIcon from '@material-ui/icons/CancelRounded'; +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import EmailRoundedIcon from '@material-ui/icons/EmailRounded'; +//routers +import { Link } from 'react-router-dom'; + +let currPage = 0; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const CommunityQuestion = () => { + const { state, dispatch } = useContext(Store); + + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = [ + "ID", + "DATA DE CONTATO", + "NOME", + "EMAIL", + "MENSAGEM", + "VISUALIZAR" + ]; //Labels from Table + + const [anchorEl, setAnchorEl] = React.useState(null); + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + + //Works with the filter + const [showMessageFilter, setShowMessageFilter] = useState(false); + const [showEmailFilter, setShowEmailFilter] = useState(false); + const [showNameFilter, setShowNameFilter] = useState(false); + const [message, setMessage] = useState(''); + const [email, setEmail] = useState(''); + const [name, setName] = useState(''); + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + //handle with the message filter + const MessageFilterHandler = async (e) => { + setMessage(e.target.value) + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(Url("contacts", `"message" : "${message}"`, currPage, 'DESC'), headers).then((res) => { + console.log(res); + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Não achamos nada na nossa base de dados!", true, "warning", "#FA8072"); + } + }); + } + + //handle with the email filter + const EmailFilterHandler = (e) => { + setEmail(e.target.value) + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(Url("contacts", `"email" : "${email}"`, currPage, 'DESC'), headers).then((res) => { + console.log(res); + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Não achamos nada na nossa base de dados!", true, "warning", "#FA8072"); + } + }); + } + + //handle with the name filter + const NameFilterHandler = (e) => { + setName(e.target.value) + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(Url("contacts", `"name" : "${name}"`, currPage, 'DESC'), headers).then((res) => { + console.log(res); + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Não achamos nada na nossa base de dados!", true, "warning", "#FA8072"); + } + }); + } + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? 'simple-popover' : undefined; + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(api, headers).then((res) => { + if (res.state) { + const arrData = [...res.data]; + if (arrData.length === 0) { + HandleSnack( + "Não há mais dados para serem carregados", + true, + "warning", + "#FFC125" + ); + } else { + const arrItems = [...items]; + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData); + setItems(arrResult.concat(ADD_ONE_LENGHT)); + } + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsLoadingMoreItems(false); + }); + }; + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + 'If-None-Match': null + }; + + GetFullList(api, headers).then((res) => { + console.log(res); + if (res.state) { + HandleSnack( + "A lista de dados foi atualizada", + true, + "success", + "#228B22" + ); + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsUpdating(false); + }); + }; + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + //getting data from server + useEffect(() => { + GetFullList(Url("contacts", "", `${currPage}`, "DESC")).then( + (res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + setIsLoaded(true); + setError(false); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else if (CheckUserPermission()) { + return <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + + <Paper style={{ padding: "1em" }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4">Dúvidas da comunidade</Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0; + UpdateHandler( + Url("contacts", "", `${currPage}`, "DESC") + ); + }} + startIcon={<UpdateRoundedIcon />} + > + {isUpdating ? <CircularProgress size={24} /> : "Atualizar"} + </Button> + </Grid> + <Grid item> + <Button + variant="contained" + color="secondary" + onClick={(e) => { + currPage = 0; + UpdateHandler( + Url("contacts", "", `${currPage}`, "DESC") + ); + handleClick(e); + }} + startIcon={<FilterListRoundedIcon />} + > + Filtrar + </Button> + <Popover + id={id} + open={open} + anchorEl={anchorEl} + onClose={handleClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + > + <Button + onClick={() => setShowEmailFilter(!showEmailFilter)} + color={showEmailFilter ? 'primary' : 'default'} + variant='text' + > + EMAIL + </Button> + + <Button + onClick={() => setShowMessageFilter(!showMessageFilter)} + color={showMessageFilter ? 'primary' : 'default'} + variant='text' + > + MENSAGEM + </Button> + + <Button + onClick={() => setShowNameFilter(!showNameFilter)} + color={showNameFilter ? 'primary' : 'default'} + variant='text' + > + NOME + </Button> + </Popover> + </Grid> + </Grid> + </Grid> + </Grid> + + <div style={{ height: '1.5em' }}></div> + + <Grid item xs={12}> + <Grid container justify="space-between" spacing={3}> + { + showMessageFilter ? + <Grid item> + <TextField + label='Mensagem' + type="search" + onChange={(e) => MessageFilterHandler(e)} + /> + <IconButton + size="small" + color="primary" + onClick={() => { + currPage = 0; + UpdateHandler( + Url("contacts", "", `${currPage}`, "DESC") + ); + setShowMessageFilter(false) + }} + > + <CancelRoundedIcon /> + </IconButton> + </Grid> : null + } + { + showEmailFilter ? + <Grid item> + <TextField + label='Email' + type="search" + onChange={(e) => EmailFilterHandler(e)} + /> + <IconButton + size="small" + color="primary" + onClick={() => { + currPage = 0; + UpdateHandler( + Url("contacts", "", `${currPage}`, "DESC") + ); + setShowEmailFilter(false) + }} + > + <CancelRoundedIcon /> + </IconButton> + </Grid> : null + } + { + showNameFilter ? + <Grid item> + <TextField + label='Nome' + type="search" + onChange={(e) => NameFilterHandler(e)} + /> + <IconButton + size="small" + color="primary" + onClick={() => { + currPage = 0; + UpdateHandler( + Url("contacts", "", `${currPage}`, "DESC") + ); + setShowNameFilter(false) + }} + > + <CancelRoundedIcon /> + </IconButton> + </Grid> : null + } + </Grid> + </Grid> + </Paper> + + <div style={{ height: "2em" }}></div> + + <TableData top={TOP_LABELS}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index} style={{ padding: "1em" }}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color="primary" + variant="text" + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++; + if (showMessageFilter) { + LoadMoreItens( + Url("contacts", `"message" : "${message}"`, `${currPage}`, "DESC") + ); + } else if (showEmailFilter) { + LoadMoreItens( + Url("contacts", `"email" : "${email}"`, `${currPage}`, "DESC") + ); + } else if (showNameFilter) { + LoadMoreItens( + Url("contacts", `"name" : "${name}"`, `${currPage}`, "DESC") + ); + } else { + LoadMoreItens( + Url("contacts", "", `${currPage}`, "DESC") + ); + } + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row"> + {row.id} + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {row.name} + </StyledTableCell> + <StyledTableCell align="right"> + { + row.email ? + <Link to={`/admin/sendEmail/${row.email}`} style={{ textDecoration: 'none' }}> + <Button + variant='text' + color='primary' + startIcon={<EmailRoundedIcon />} + > + {row.email} + </Button> + </Link> : null + } + </StyledTableCell> + <StyledTableCell align="right"> + {row.message} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/CommunityQuestion/${row.id}`}> + <IconButton> + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + </> + } else return <Unauthorized /> +} +export default CommunityQuestion; diff --git a/src/Admin/Pages/Pages/SubPages/Complaints.js b/src/Admin/Pages/Pages/SubPages/Complaints.js new file mode 100644 index 0000000000000000000000000000000000000000..8b4ad0c710c45308b58c76b1f4211b4920553ea8 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Complaints.js @@ -0,0 +1,571 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState, useContext } from "react"; +import moment from "moment"; +//imports from local files +import TableData from "../../../Components/Components/Table"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import { Url } from "../../../Filters"; +import { GetFullList } from "../../../Services"; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports from material ui +import { withStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import MenuItem from "@material-ui/core/MenuItem"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import IconButton from "@material-ui/core/IconButton"; +import { Button, Typography, Paper, Grid } from "@material-ui/core"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import LaunchRoundedIcon from "@material-ui/icons/LaunchRounded"; +//routers +import { Link } from "react-router-dom"; +import Unauthorized from "../../../Components/Components/Unauthorized"; + +let currPage = 0; +let currIdFilter; +let currTypeFilter; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Complaints = () => { + const { state, dispatch } = useContext(Store); + + const PORTAL_MEC = "https://plataformaintegrada.mec.gov.br/"; + + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = [ + "ESTADO DO RECURSO", + "ID", + "DESCRIÇÃO", + "ID OBJETO", + "DATA(MM/DD/YYYY)", + "VISUALIZAR", + "VISITAR", + ]; //Labels from Table + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + + const [showFilter, setShowFilter] = useState(false); + + const [option, setOption] = useState("Todos os usuários"); //labels of the text field 'to' + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + const StateOptions = [ + { id: 0, name: "Pendente" }, + { id: 1, name: "Ativo" }, + { id: 2, name: "Bloqueado / Removido" }, + ]; + + const ComplaintReasons = [ + { id: 1, name: "Viola direitos autorais" }, + { id: 2, name: "Conteúdo ofensivo/abusivo" }, + { id: 3, name: "Conta falsa" }, + { id: 4, name: "Spam" }, + { id: 5, name: "Descrição diverge do conteúdo" }, + ]; + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + "If-None-Match": null, + }; + + GetFullList(api, headers).then((res) => { + if (res.state) { + const arrData = [...res.data]; + if (arrData.length === 0) { + HandleSnack( + "Não há mais dados para serem carregados", + true, + "warning", + "#FFC125" + ); + } else { + const arrItems = [...items]; + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData); + setItems(arrResult.concat(ADD_ONE_LENGHT)); + } + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsLoadingMoreItems(false); + }); + }; + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true); + const headers = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "access-token": sessionStorage.getItem("@portalmec/accessToken"), + client: sessionStorage.getItem("@portalmec/clientToken"), + uid: sessionStorage.getItem("@portalmec/uid"), + "If-None-Match": null, + }; + + GetFullList(api, headers).then((res) => { + console.log(res); + if (res.state) { + HandleSnack( + "A lista de dados foi atualizada", + true, + "success", + "#228B22" + ); + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsUpdating(false); + }); + }; + + const handleChange = (e, type) => { + const value = e.target.value; + console.log(e); + setOption(value); + }; + + const ApplyFilter = (id, type) => { + currPage = 0; + currIdFilter = id; + currTypeFilter = type; + GetFullList( + Url("complaints", `"${currTypeFilter}" : "${currIdFilter}"`, `${currPage}`, "DESC") + ).then((res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + HandleSnack("Filtro aplicado com sucesso", true, "success", "#228B22"); + setIsLoaded(true); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + } + }); + }; + + const DescriptionHandler = (e) => { + currPage = 0; + currTypeFilter = "description"; + currIdFilter = e.target.value; + + GetFullList( + Url("complaints", `"${currTypeFilter}" : "${currIdFilter}"`, `${currPage}`, "DESC") + ).then((res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + HandleSnack("Filtro aplicado com sucesso", true, "success", "#228B22"); + setIsLoaded(true); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + } + }); + } + + const convertToLink = (type, id) => { + switch (type) { + case "LearningObject": + return `recurso?id=${id}/`; + case "User": + return `usuario-publico/${id}/`; + default: + return ""; + } + }; + + const ComplaintStatus = (status) => { + switch (status) { + case "accepted": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FA8072", + fontWeight: "500", + color: "#FFFAFA", + }} + > + REMOVIDO + </Paper> + ); + case "complained": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FF8C00", + fontWeight: "500", + color: "#FFFAFA", + }} + > + PENDENTE + </Paper> + ); + case "rejected": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#228B22", + fontWeight: "500", + color: "#FFFAFA", + }} + > + AVALIADO + </Paper> + ); + default: + return "NOTHING"; + } + }; + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + //getting data from server + useEffect(() => { + GetFullList(Url("complaints", "", `${currPage}`, "DESC")).then( + (res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(ADD_ONE_LENGHT)); + setIsLoaded(true); + setError(false); + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + setIsLoaded(true); + setError(true); + } + } + ); + }, []); + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else if (CheckUserPermission()) { + return ( + <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + + <Paper style={{ padding: "1em" }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4">Denúncias</Typography> + </Grid> + + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0; + UpdateHandler( + Url("complaints", "", `${currPage}`, "DESC") + ); + }} + startIcon={<UpdateRoundedIcon />} + > + {isUpdating ? <CircularProgress size={24} /> : "Atualizar"} + </Button> + </Grid> + <Grid item> + <Button + variant="contained" + color="secondary" + onClick={() => { + currPage = 0; + UpdateHandler( + Url("complaints", "", `${currPage}`, "DESC") + ); + setShowFilter(!showFilter); + }} + startIcon={<FilterListRoundedIcon />} + > + Filtrar + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + + {showFilter ? ( + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + alignContent="flex-end" + spacing={3} + xs={12} + > + <Grid item> + <TextField + select + label="Motivo" + value={option ? option : ""} + onChange={handleChange} + helperText="Por favor, selecione uma das opções" + > + {ComplaintReasons.map((option, index) => ( + <MenuItem + key={option.id} + value={option.name} + name={option.id} + onClick={() => + ApplyFilter(option.id, "complaint_reason_id") + } + > + {option.name} + </MenuItem> + ))} + </TextField> + </Grid> + <Grid item> + <TextField + select + label="Estado" + value={option ? option : ""} + onChange={handleChange} + helperText="Por favor, selecione uma das opções" + > + {StateOptions.map((option, index) => ( + <MenuItem + key={option.id} + value={option.name} + name={option.id} + onClick={() => ApplyFilter(option.id, "state")} + > + {option.name} + </MenuItem> + ))} + </TextField> + </Grid> + <Grid item> + <TextField label="Descrição" onBlur={DescriptionHandler} /> + </Grid> + </Grid> + ) : null} + </Paper> + + <div style={{ height: "2em" }}></div> + + <Grid xs={12} container> + <TableData top={TOP_LABELS}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color="primary" + variant="text" + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++; + if (showFilter) { + LoadMoreItens( + Url( + "complaints", + `"${currTypeFilter}" : "${currIdFilter}"`, + `${currPage}`, + "DESC" + ) + ); + } else { + LoadMoreItens( + Url("complaints", "", `${currPage}`, "DESC") + ); + } + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow + key={index} + style={{ flex: 1, width: "100%" }} + > + <StyledTableCell component="th" scope="row"> + {ComplaintStatus(row.state)} + </StyledTableCell> + <StyledTableCell align="right">{row.id}</StyledTableCell> + <StyledTableCell align="right"> + {row.description} + </StyledTableCell> + <StyledTableCell align="right"> + {row.complainable_id} + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/complaint/${row.id}`}> + <IconButton + onClick={() => { currPage = 0 }} + > + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + <StyledTableCell align="right"> + <Button + variant="text" + secondary={true} + startIcon={ + <LaunchRoundedIcon style={{ fill: "#FA8072" }} /> + } + > + <a + style={{ + textDecoration: "none", + color: "#FA8072", + }} + target="_blank" + href={ + PORTAL_MEC + + convertToLink( + row.complainable_type, + row.complainable_id + ) + } + > + MEC RED + </a> + </Button> + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + </Grid> + </> + ); + } else return <Unauthorized /> +}; +export default Complaints; diff --git a/src/Admin/Pages/Pages/SubPages/EducationalObjects.js b/src/Admin/Pages/Pages/SubPages/EducationalObjects.js new file mode 100644 index 0000000000000000000000000000000000000000..67eeecd2dde969b5b7928e9e10cc3c9582b7fdd5 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/EducationalObjects.js @@ -0,0 +1,555 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +import moment from 'moment'; +// Imports from local files +import TableData from "../../../Components/Components/Table"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import AlertDialog from "../../../Components/Components/AlertDialog"; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +// Imports about icon +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import IconButton from "@material-ui/core/IconButton"; +import CancelRoundedIcon from "@material-ui/icons/CancelRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import DeleteIcon from "@material-ui/icons/Delete"; +// Import from material-ui +import { withStyles, makeStyles } from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import Button from "@material-ui/core/Button"; +import Grid from "@material-ui/core/Grid"; +import { Typography, CircularProgress } from "@material-ui/core"; +import Popover from "@material-ui/core/Popover"; +import TextField from "@material-ui/core/TextField"; +// services +import { Delete, GetFullList } from "../../../Services"; +//Filters +import { Url, EditFilter } from "../../../Filters"; +//router +import { Link } from 'react-router-dom'; + +let currPage = 0; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const useStyles = makeStyles((theme) => ({ + root: { + flexGrow: 1, + }, + paper: { + padding: theme.spacing(1), + textAlign: "start", + }, + button: { + margin: theme.spacing(1), + alignSelf: "flex-end", + }, +})); + +const EducationalObjects = () => { + const classes = useStyles(); + + const addOndeLenght = [""]; + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [deleteItem, setDeleteItem] = useState({}); //Delete Item + const [isLoadingToDelete, setIsLoadingToDelete] = useState(null); + const [isUpdating, setIsUpdating] = useState(false); + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); + + const [openAlertDialog, setOpenAlertDialog] = useState(false); + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + // **************** About the PopOver Menu **************** + const [anchorEl, setAnchorEl] = React.useState(null); + const [showAuthorField, setShowAuthorField] = useState(false); //show the text field of filter by Author + const [showDescriptionField, setShowDescriptionField] = useState(false); //show the text field of the filter by description + const [showStandadSearch, setShowStandarSearchField] = useState(false); + + const [search, setSeacrh] = useState(""); + const [author, setAuthor] = useState(""); + const [description, setDescription] = useState(""); + + const AuthorHandler = () => { + setShowAuthorField(!showAuthorField); + }; + + const DescHandler = () => { + setShowDescriptionField(!showDescriptionField); + }; + + const StandartHandler = () => { + setShowStandarSearchField(!showStandadSearch); + }; + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? "simple-popover" : undefined; + + const OnChangeSearchHandler = (e) => { + setSeacrh(e.target.value); + Filter(Url("learning_objects", `"name" : "${search}"`, currPage, "DESC")); + }; + + const onChangeAuthorHandler = (e) => { + setAuthor(e.target.value); + Filter(Url("learning_objects", `"author" : "${author}"`, currPage, "DESC")); + }; + + const onChangeDescriptionHandler = (e) => { + setDescription(e.target.value); + Filter(Url("learning_objects", `"description" : "${description}"`, currPage, "DESC")); + }; + // **************** About the PopOverMenu **************** + + //Controlls the state of the Alert Dialog + const HandleStateAlertDialog = (i) => { + const obj = { ...items[i] }; + setDeleteItem(obj); + setOpenAlertDialog(!openAlertDialog); + }; + + //Controlls the state and the informations of the snack + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + //Defines which row must show the circular progress + const HandleStateCircularProgress = (i) => { + setIsLoadingToDelete(i); + }; + + //Filters the search + const Filter = (api) => { + GetFullList(api).then((res) => { + if (res.state) { + const arrData = [...res.data]; + setItems(arrData.concat(addOndeLenght)); + } else { + setError(true); + } + }); + }; + + //This function updates List every time the content of the api changes + const UpdtateListData = (api) => { + GetFullList(api).then((res) => { + const arrData = [...res.data]; + if (res.state) { + setItems(arrData.concat(addOndeLenght)); + HandleSnack( + "A lista de dados foi atualizada", + true, + "success", + "#228B22" + ); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + setIsUpdating(false); + }); + }; + + //Called when user want to delete one institution + async function DeleteHandler() { + const id = deleteItem.id; + HandleStateAlertDialog(null); + Delete(EditFilter("institutions", id)).then((res) => { + if (res) { + HandleSnack( + "A instituição foi deletada com sucesso", + true, + "success", + "#228B22" + ); + currPage = 0; + UpdtateListData(Url("institutions", "", `${currPage}`, "DESC")); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + HandleStateCircularProgress(null); + }); + } + + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true); + GetFullList(api).then((res) => { + if (res.state) { + const arrData = [...res.data]; + console.log(arrData); + if (arrData.length === 0) { + HandleSnack( + "Não há mais dados para serem carregados", + true, + "warning", + "#FFC125" + ); + } else { + const arrItems = [...items]; + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData); + setItems(arrResult.concat(addOndeLenght)); + } + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsLoadingMoreItems(false); + }); + }; + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + useEffect(() => { + GetFullList(Url("learning_objects", "", `${currPage}`, "DESC")) + .then( + (result) => { + if(result.state){ + setIsLoaded(true); + setItems(result.data.concat(addOndeLenght)); + } else{ + setError(true); + } + + }, + ); + + }, []); + + if (error) { + return <div>Error: {error.message}</div>; + } + if (!isLoaded) { + return <LoadingSpinner text="Carregando..."/> + } else { + //Words that defines that column + const topTable = [ + "CRIADO EM", + "NOME", + "DESCRIÇÃO", + "AUTOR", + "SCORE", + "VISUALIZAR", + "DELETAR", + ]; + + //Buttons from PopOverMenu + const flatButtonsFromPopOverMenu = [ + { + label: "Pesquisa padrão", + onClick: StandartHandler, + color: showStandadSearch ? "primary" : "default", + }, + { + label: "Autor", + onClick: AuthorHandler, + color: showAuthorField ? "primary" : "default", + }, + { + label: "Descrição", + onClick: DescHandler, + color: showDescriptionField ? "primary" : "default", + }, + ]; + + //Field of the Filter + const TextFieldOfTheFilter = [ + { + label: "Pesquisar", + show: showStandadSearch, + onChange: (event) => OnChangeSearchHandler(event), + hide: StandartHandler, + }, + { + label: "Autor", + show: showAuthorField, + onChange: (event) => onChangeAuthorHandler(event), + hide: AuthorHandler, + }, + { + label: "Descrição", + show: showDescriptionField, + onChange: (event) => onChangeDescriptionHandler(event), + hide: DescHandler, + }, + ]; + + return ( + <div> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + {/************** Start of the header **************/} + <Paper style={{ padding: "1em" }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography className={classes.paper} variant="h4"> + Lista de objetos educacionais + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + aria-describedby={id} + variant="contained" + color="secondary" + className={classes.button} + onClick={(event) => { + currPage = 0; + UpdtateListData(Url("learning_objects", "", `${currPage}`, "DESC")); + handleClick(event) + }} + startIcon={ + <FilterListRoundedIcon style={{ fill: "white" }} /> + } + > + Filtrar + </Button> + <Popover + id={id} + open={open} + anchorEl={anchorEl} + onClose={handleClose} + anchorOrigin={{ + vertical: "bottom", + horizontal: "center", + }} + transformOrigin={{ + vertical: "top", + horizontal: "center", + }} + > + {flatButtonsFromPopOverMenu.map((flat, index) => ( + <Button + key={index} + onClick={flat.onClick} + color={flat.color} + > + {flat.label} + </Button> + ))} + </Popover> + </Grid> + + <Grid item> + <Button + variant="contained" + color="secondary" + className={classes.button} + startIcon={<UpdateRoundedIcon />} + disabled={isUpdating} + onClick={() => { + currPage = 0; + setIsUpdating(true); + UpdtateListData( + Url("learning_objects", "", `${currPage}`, "DESC") + ); + }} + > + {isUpdating ? <CircularProgress size={24} /> : "Atualizar"} + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + <Grid item xs={12}> + <Grid container justify="space-between" spacing={3}> + {TextFieldOfTheFilter.map((field, index) => ( + <Grid item key={index}> + {field.show ? ( + <div> + <TextField + id={index} + label={field.label} + type="search" + onChange={field.onChange} + /> + <IconButton + size="small" + color="primary" + onClick={field.hide} + > + <CancelRoundedIcon /> + </IconButton> + </div> + ) : null} + </Grid> + ))} + </Grid> + </Grid> + </Paper> + {/************** End of the header **************/} + + <div style={{ height: "2em" }}></div> + + {/************** Start of display data in table **************/} + <TableData top={topTable}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color="primary" + variant="text" + startIcon={<AddRoundedIcon />} + disabled={ + isLoadingMoreItems + } + onClick={() => { + currPage++ + if(showAuthorField) LoadMoreItens(Url("learning_objects", `"author" : "${author}"`, currPage, "DESC")) + else if(showDescriptionField) LoadMoreItens(Url("learning_objects", `"description" : "${description}"`, currPage, "DESC")) + else if(showStandadSearch) LoadMoreItens(Url("learning_objects", `"name" : "${search}"`, currPage, "DESC")) + else LoadMoreItens(Url('learning_objects', '', `${currPage}`, 'DESC')) + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {row.name} + </StyledTableCell> + <StyledTableCell align="right"> + {row.description} + </StyledTableCell> + <StyledTableCell align="right"> + {row.author} + </StyledTableCell> + <StyledTableCell align="right"> + {row.score} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/learningObject/${row.id}`}> + <IconButton onClick={() => { + currPage = 0 + }}> + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + <StyledTableCell align="right"> + {isLoadingToDelete === index ? ( + <CircularProgress size={24} color="primary" /> + ) : ( + <IconButton + onClick={() => { + HandleStateAlertDialog(index); + HandleStateCircularProgress(index); + }} + > + <DeleteIcon style={{ fill: "#FF0000" }} /> + </IconButton> + )} + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + {/************** End of display data in table **************/} + + {/* This alert will be displayed if the user click to delete an institution */} + <AlertDialog + open={openAlertDialog} + OnDelete={DeleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpenAlertDialog(false); + HandleStateCircularProgress(null); + }} + /> + </div> + ) + } +}; + +export default EducationalObjects; diff --git a/src/Admin/Pages/Pages/SubPages/Inframe.js b/src/Admin/Pages/Pages/SubPages/Inframe.js new file mode 100644 index 0000000000000000000000000000000000000000..cb261c41563de5b2e90c02bae286a72d449d690b --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Inframe.js @@ -0,0 +1,40 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React from 'react'; +import Welcome from '../../../Components/Components/Welcome'; + +//This file show the charts + +export default class IframeComponent extends React.Component { + render() { + return ( + <div> + <Welcome/> + <div style={{height : '1em'}}></div> + <iframe + src='https://metabase.c3sl.ufpr.br/public/dashboard/8ada315d-b8df-4b18-b7fb-d06b0ac64623' + height='800px' + width='100%' + // allowTransparency={true} + frameBorder={0} + /> + </div> + ) + } +} diff --git a/src/Admin/Pages/Pages/SubPages/Institutions.js b/src/Admin/Pages/Pages/SubPages/Institutions.js new file mode 100644 index 0000000000000000000000000000000000000000..8f73792d6cc5dbe283a2fdf4bace69e4c2e97c44 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Institutions.js @@ -0,0 +1,630 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState, useEffect } from "react"; +// Imports from local files +import TableData from "../../../Components/Components/Table"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import AlertDialog from "../../../Components/Components/AlertDialog"; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +// Imports about icon +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import IconButton from "@material-ui/core/IconButton"; +import CancelRoundedIcon from "@material-ui/icons/CancelRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import DeleteIcon from "@material-ui/icons/Delete"; +// Import from material-ui +import { withStyles, makeStyles } from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import Button from "@material-ui/core/Button"; +import Grid from "@material-ui/core/Grid"; +import { Typography, CircularProgress } from "@material-ui/core"; +import Popover from "@material-ui/core/Popover"; +import TextField from "@material-ui/core/TextField"; +// services +import { Delete, GetFullList } from "../../../Services"; +//Filters +import { Url, GetOneOfAllUrl, EditFilter } from "../../../Filters"; +//router +import { Link } from 'react-router-dom'; + +let currPage = 0; +let transformListToAsc = false + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const useStyles = makeStyles((theme) => ({ + root: { + flexGrow: 1, + }, + paper: { + padding: theme.spacing(1), + textAlign: "start", + }, + button: { + margin: theme.spacing(1), + alignSelf: "flex-end", + }, +})); + +const Institutions = () => { + const classes = useStyles(); + + const addOndeLenght = [""]; + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [deleteItem, setDeleteItem] = useState({}); //Delete Item + const [isLoadingToDelete, setIsLoadingToDelete] = useState(null); + const [isUpdating, setIsUpdating] = useState(false); + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); + + const [openAlertDialog, setOpenAlertDialog] = useState(false); + + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + // **************** About the PopOver Menu **************** + const [anchorEl, setAnchorEl] = React.useState(null); + const [showCityField, setShowCityField] = useState(false); //show the text field of filter by city + const [showContryField, setShowCountryField] = useState(false); //show the text field of the filter by country + const [showDescriptionField, setShowDescriptionField] = useState(false); //show the text field of the filter by description + const [showStandadSearch, setShowStandarSearchField] = useState(false); + + const [isFilteringByName, setisFilteringByName] = useState(false); + const [isFilteringByCity, setisFilteringByCity] = useState(false); + const [isFilteringByCounty, setisFilteringByCountry] = useState(false); + const [isFilteringByDesc, setisFilteringByDesc] = useState(false); + + const [search, setSeacrh] = useState(""); + const [city, setCity] = useState(""); + const [country, setCountry] = useState(""); + const [description, setDescription] = useState(""); + + const CityHandler = () => { + currPage = 0; + transformListToAsc = false + setShowCityField(!showCityField); + setisFilteringByCity(!isFilteringByCity); + UpdtateListData(Url("institutions", "", `${currPage}`, "DESC")); + }; + + const CountryHandler = () => { + currPage = 0; + transformListToAsc = false + setShowCountryField(!showContryField); + UpdtateListData(Url("institutions", "", `${currPage}`, "DESC")); + setisFilteringByCountry(!isFilteringByCounty); + }; + + const DescHandler = () => { + currPage = 0; + transformListToAsc = false + setShowDescriptionField(!showDescriptionField); + setisFilteringByDesc(!isFilteringByDesc); + UpdtateListData(Url("institutions", "", `${currPage}`, "DESC")); + }; + + const StandartHandler = () => { + currPage = 0; + transformListToAsc = false + setShowStandarSearchField(!showStandadSearch); + setisFilteringByName(!isFilteringByName); + UpdtateListData(Url("institutions", "", `${currPage}`, "DESC")); + }; + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? "simple-popover" : undefined; + + const OnChangeSearchHandler = (e) => { + setSeacrh(e.target.value); + Filter(GetOneOfAllUrl("institutions", `"name" : "${search}"`)); + }; + + const onChangeCityHandler = (e) => { + setCity(e.target.value); + Filter(GetOneOfAllUrl("institutions", `"city" : "${city}"`)); + }; + + const onChangeCountryHandler = (e) => { + setCountry(e.target.value); + Filter(GetOneOfAllUrl("institutions", `"country" : "${country}"`)); + }; + + const onChangeDescriptionHandler = (e) => { + setDescription(e.target.value); + Filter(GetOneOfAllUrl("institutions", `"description" : "${description}"`)); + }; + // **************** About the PopOverMenu **************** + + //Controlls the state of the Alert Dialog + const HandleStateAlertDialog = (i) => { + const obj = { ...items[i] }; + setDeleteItem(obj); + setOpenAlertDialog(!openAlertDialog); + }; + + //Controlls the state and the informations of the snack + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + //Defines which row must show the circular progress + const HandleStateCircularProgress = (i) => { + setIsLoadingToDelete(i); + }; + + //Filters the search + const Filter = (api) => { + GetFullList(api).then((res) => { + const arrData = [...res.data]; + if (res.state) { + transformListToAsc = false + setItems(arrData.concat(addOndeLenght)); + } else { + setError(true); + } + }); + }; + + //This function updates List every time the content of the api changes + const UpdtateListData = (api) => { + GetFullList(api).then((res) => { + const arrData = [...res.data]; + if (res.state) { + setItems(arrData.concat(addOndeLenght)); + HandleSnack( + "A lista de dados foi atualizada", + true, + "success", + "#228B22" + ); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + setIsUpdating(false); + }); + }; + + //Called when user want to delete one institution + async function DeleteHandler() { + const id = deleteItem.id; + HandleStateAlertDialog(null); + Delete(EditFilter("institutions", id)).then((res) => { + if (res) { + HandleSnack( + "A instituição foi deletada com sucesso", + true, + "success", + "#228B22" + ); + currPage = 0; + transformListToAsc = false + UpdtateListData(Url("institutions", "", `${currPage}`, "DESC")); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + HandleStateCircularProgress(null); + }); + } + + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true); + GetFullList(api).then((res) => { + if (res.state) { + const arrData = [...res.data]; + console.log(arrData); + if (arrData.length === 0) { + HandleSnack( + "Não há mais dados para serem carregados", + true, + "warning", + "#FFC125" + ); + } else { + const arrItems = [...items]; + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData); + setItems(arrResult.concat(addOndeLenght)); + } + } else { + HandleSnack("Erro ao carregar os dados", true, "warning", "#FA8072"); + } + setIsLoadingMoreItems(false); + }); + }; + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + GetFullList(Url('institutions', '', `${currPage}`, 'ASC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(addOndeLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + GetFullList(Url('institutions', '', `${currPage}`, 'DESC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(addOndeLenght)) + } else { + HandleSnack('institutions ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + } + + useEffect(() => { + GetFullList(Url("institutions", "", `${currPage}`, "DESC")) + .then( + (result) => { + if (result.state) { + setIsLoaded(true); + setItems(result.data.concat(addOndeLenght)); + } else setError(true); + }, + ); + + }, []); + + if (error) { + return <div>Error: {error.message}</div>; + } + if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else { + //Words that defines that column + const topTable = [ + "ID", + "NOME", + "DESCRIÇÃO", + "CIDADE", + "PAÃS", + "VISUALIZAR", + "DELETAR", + ]; + + //Buttons from PopOverMenu + const flatButtonsFromPopOverMenu = [ + { + label: "Pesquisa padrão", + onClick: StandartHandler, + color: showStandadSearch ? "primary" : "default", + }, + { + label: "Cidade", + onClick: CityHandler, + color: showCityField ? "primary" : "default", + }, + { + label: "PaÃs", + onClick: CountryHandler, + color: showContryField ? "primary" : "default", + }, + { + label: "Descrição", + onClick: DescHandler, + color: showDescriptionField ? "primary" : "default", + }, + ]; + + //Field of the Filter + const TextFieldOfTheFilter = [ + { + label: "Pesquisar", + show: showStandadSearch, + onChange: (event) => OnChangeSearchHandler(event), + hide: StandartHandler, + }, + { + label: "Cidade", + show: showCityField, + onChange: (event) => onChangeCityHandler(event), + hide: CityHandler, + }, + { + label: "PaÃs", + show: showContryField, + onChange: (event) => onChangeCountryHandler(event), + hide: CountryHandler, + }, + { + label: "Descrição", + show: showDescriptionField, + onChange: (event) => onChangeDescriptionHandler(event), + hide: DescHandler, + }, + ]; + + return ( + <div> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + {/************** Start of the header **************/} + <Paper style={{ padding: "1em" }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography className={classes.paper} variant="h4"> + Lista de Instituições + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + aria-describedby={id} + variant="contained" + color="secondary" + className={classes.button} + onClick={handleClick} + startIcon={ + <FilterListRoundedIcon style={{ fill: "white" }} /> + } + > + Filtrar + </Button> + <Popover + id={id} + open={open} + anchorEl={anchorEl} + onClose={handleClose} + anchorOrigin={{ + vertical: "bottom", + horizontal: "center", + }} + transformOrigin={{ + vertical: "top", + horizontal: "center", + }} + > + {flatButtonsFromPopOverMenu.map((flat, index) => ( + <Button + key={index} + onClick={flat.onClick} + color={flat.color} + > + {flat.label} + </Button> + ))} + </Popover> + </Grid> + + <Grid item> + <Link style={{ textDecoration: 'none' }} to={'/admin/InstitutionCreate'}> + <Button + variant="contained" + color="secondary" + className={classes.button} + startIcon={<AddRoundedIcon style={{ fill: "white" }} />} + onClick={() => { + currPage = 0 + transformListToAsc = false + }} + > + Novo + </Button> + </Link> + </Grid> + + <Grid item> + <Button + variant="contained" + color="secondary" + className={classes.button} + startIcon={<UpdateRoundedIcon />} + disabled={isUpdating} + onClick={() => { + currPage = 0; + transformListToAsc = false + setIsUpdating(true); + UpdtateListData( + Url("institutions", "", `${currPage}`, "DESC") + ); + }} + > + {isUpdating ? <CircularProgress /> : "Atualizar"} + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + <Grid item xs={12}> + <Grid container justify="space-between" spacing={3}> + {TextFieldOfTheFilter.map((field, index) => ( + <Grid item key={index}> + {field.show ? ( + <div> + <TextField + id={index} + label={field.label} + type="search" + onChange={field.onChange} + /> + <IconButton + size="small" + color="primary" + onClick={field.hide} + > + <CancelRoundedIcon /> + </IconButton> + </div> + ) : null} + </Grid> + ))} + </Grid> + </Grid> + </Paper> + {/************** End of the header **************/} + + <div style={{ height: "2em" }}></div> + + {/************** Start of display data in table **************/} + <TableData top={topTable} onIconPressed={InvertList}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color="primary" + variant="text" + startIcon={<AddRoundedIcon />} + disabled={ + isLoadingMoreItems || + isFilteringByCity || + isFilteringByCounty || + isFilteringByDesc || + isFilteringByName + } + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('institutions', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('institutions', '', `${currPage}`, 'DESC')) + } + }} + > + {isLoadingMoreItems ? ( + <CircularProgress /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row"> + {row.id} + </StyledTableCell> + <StyledTableCell align="right">{row.name}</StyledTableCell> + <StyledTableCell align="right"> + {row.description} + </StyledTableCell> + <StyledTableCell align="right">{row.city}</StyledTableCell> + <StyledTableCell align="right"> + {row.country} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/institution/${row.id}`}> + <IconButton onClick={() => { + currPage = 0 + transformListToAsc = false + }}> + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + <StyledTableCell align="right"> + {isLoadingToDelete === index ? ( + <CircularProgress size={24} color="primary" /> + ) : ( + <IconButton + onClick={() => { + HandleStateAlertDialog(index); + HandleStateCircularProgress(index); + }} + > + <DeleteIcon style={{ fill: "#FF0000" }} /> + </IconButton> + )} + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + {/************** End of display data in table **************/} + + {/* This alert will be displayed if the user click to delete an institution */} + <AlertDialog + open={openAlertDialog} + OnDelete={DeleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpenAlertDialog(false); + HandleStateCircularProgress(null); + }} + /> + </div> + ) + } +}; + +export default Institutions; diff --git a/src/Admin/Pages/Pages/SubPages/Languages.js b/src/Admin/Pages/Pages/SubPages/Languages.js new file mode 100644 index 0000000000000000000000000000000000000000..2d0dd3910aa24a1751a0c98269c257ffeb9a9608 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Languages.js @@ -0,0 +1,351 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState } from 'react' +//imports from local files +import TableData from '../../../Components/Components/Table'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import AlertDialog from "../../../Components/Components/AlertDialog"; +import { Url } from '../../../Filters'; +import { GetFullList, Delete } from '../../../Services'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +import { DeleteFilter } from '../../../Filters'; +//imports from material ui +import { withStyles } from '@material-ui/core/styles'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableRow from '@material-ui/core/TableRow'; +import IconButton from '@material-ui/core/IconButton'; +import { Button, Typography, Paper, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import UpdateRoundedIcon from '@material-ui/icons/UpdateRounded'; +import EditRoundedIcon from '@material-ui/icons/EditRounded'; +import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded'; +//router +import { Link } from 'react-router-dom'; + +let currPage = 0; +let transformListToAsc = false; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Languages = () => { + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = ['ID', 'Nome', 'Code', 'Editar', 'Deletar'] //Labels from Table + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false) //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + const [openAlertDialog, setOpenAlertDialog] = useState(false); //controlls the state od alert dialog + + const [deleteItem, setDeleteItem] = useState({}); //Delete Item + const [isLoadingToDelete, setIsLoadingToDelete] = useState(null); + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true) + GetFullList(api).then(res => { + if (res.state) { + const arrData = [...res.data] + if (arrData.length === 0) { + HandleSnack('Não há mais dados para serem carregados', true, 'warning', '#FFC125') + } else { + const arrItems = [...items] + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData) + setItems(arrResult.concat(ADD_ONE_LENGHT)) + } + + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsLoadingMoreItems(false) + }) + } + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true) + GetFullList(api).then(res => { + if (res.state) { + HandleSnack('A lista de dados foi atualizada', true, 'success', '#228B22') + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsUpdating(false) + }) + } + + //handle Delete + async function DeleteHandler() { + const id = deleteItem.id; + HandleStateAlertDialog(null); + Delete(DeleteFilter("languages", id)).then((res) => { + if (res) { + HandleSnack( + "A lÃngua foi deletada com sucesso", + true, + "success", + "#228B22" + ); + currPage = 0; + transformListToAsc = false + UpdateHandler(Url("languages", "", `${currPage}`, "DESC")); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + HandleStateCircularProgress(null); + }); + } + + const HandleStateCircularProgress = (i) => { + setIsLoadingToDelete(i); + }; + + const HandleStateAlertDialog = (i) => { + const obj = { ...items[i] }; + setDeleteItem(obj); + setOpenAlertDialog(!openAlertDialog); + }; + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + GetFullList(Url('languages', '', `${currPage}`, 'ASC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + GetFullList(Url('languages', '', `${currPage}`, 'DESC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + } + + //getting data from server + useEffect(() => { + GetFullList(Url('languages', '', `${currPage}`, 'DESC')) + .then( + (result) => { + if (result.state) { + setIsLoaded(true); + setItems(result.data.concat(ADD_ONE_LENGHT)); + } else { + setError(true); + } + }, + ) + }, []); + + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else { + return ( + <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + + <Paper style={{ padding: '1em' }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4"> + Linguagens + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('languages', '', `${currPage}`, 'DESC')) + }} + startIcon={<UpdateRoundedIcon />} + > + { + isUpdating ? <CircularProgress size={24} /> : 'Atualizar' + } + </Button> + </Grid> + <Grid item> + + <Link style={{ textDecoration: 'none' }} to={'/admin/languageCreate'}> + <Button + variant="contained" + color="secondary" + startIcon={<AddRoundedIcon />} + > + Novo + </Button> + </Link> + </Grid> + </Grid> + </Grid> + </Grid> + </Paper> + + <div style={{ height: '2em' }}></div> + + <TableData + top={TOP_LABELS} + onIconPressed={InvertList} + > + <TableBody> + {items.map((row, index) => ( + index === items.length - 1 ? + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color='primary' + variant='text' + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('languages', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('languages', '', `${currPage}`, 'DESC')) + } + }} + > + { + isLoadingMoreItems ? <CircularProgress size={24} /> : 'Carregar mais itens' + } + </Button> + </StyledTableCell> + </StyledTableRow> + + : + + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row">{row.id}</StyledTableCell> + <StyledTableCell align="right">{row.name}</StyledTableCell> + <StyledTableCell align="right">{row.code}</StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/languageEdit/${row.id}`}> + <IconButton> + <EditRoundedIcon style={{ fill: '#00bcd4' }} /> + </IconButton> + </Link> + </StyledTableCell> + <StyledTableCell align="right"> + {isLoadingToDelete === index ? ( + <CircularProgress size={24} color="primary" /> + ) : ( + <IconButton + onClick={() => { + HandleStateAlertDialog(index); + HandleStateCircularProgress(index); + }} + > + <DeleteRoundedIcon style={{ fill: "#FF0000" }} /> + </IconButton> + )} + </StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </TableData> + + {/* This alert will be displayed if the user click to delete an institution */} + <AlertDialog + open={openAlertDialog} + OnDelete={DeleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpenAlertDialog(false); + HandleStateCircularProgress(null); + }} + /> + </> + ); + } +} +export default Languages; \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/NoteVariables.js b/src/Admin/Pages/Pages/SubPages/NoteVariables.js new file mode 100644 index 0000000000000000000000000000000000000000..3d242bb22794370daa2d86b81a56dd1d88c27627 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/NoteVariables.js @@ -0,0 +1,291 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ +import React, { useEffect, useState } from 'react'; +//Material ui componets +import { withStyles } from '@material-ui/core/styles'; +import TableBody from '@material-ui/core/TableBody'; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import TableCell from '@material-ui/core/TableCell'; +import TableRow from '@material-ui/core/TableRow'; +import CheckRoundedIcon from "@material-ui/icons/CheckRounded"; +import BlockRoundedIcon from "@material-ui/icons/BlockRounded"; +import IconButton from '@material-ui/core/IconButton'; +import VisibilityIcon from '@material-ui/icons/Visibility'; +import { Button, Typography } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import UpdateRoundedIcon from '@material-ui/icons/UpdateRounded' +//Local files +import TableData from '../../../Components/Components/Table'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//Services +import { Url } from '../../../Filters'; +import { GetFullList } from '../../../Services'; +//routers +import { Link } from 'react-router-dom'; + +let currPage = 0; //var that controlls the current page that we are +let transformListToAsc = false; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const NoteVariables = () => { + const AddOneLenght = ['']; + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false) + const [isUpdating, setIsUpdating] = useState(false) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true) + GetFullList(api).then(res => { + if (res.state) { + const arrData = [...res.data] + if (arrData.length === 0) { + HandleSnack('Não há mais dados para serem carregados', true, 'warning', '#FFC125') + } else { + const arrItems = [...items] + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData) + setItems(arrResult.concat(AddOneLenght)) + } + + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsLoadingMoreItems(false) + }) + } + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + GetFullList(Url('scores', '', `${currPage}`, 'ASC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(AddOneLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + GetFullList(Url('scores', '', `${currPage}`, 'DESC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(AddOneLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + } + + const UpdateHandler = async (api) => { + setIsUpdating(true) + GetFullList(api).then(res => { + if (res.state) { + HandleSnack('A lista de dados foi atualizada', true, 'success', '#228B22') + const arrData = [...res.data] + setItems(arrData.concat(AddOneLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsUpdating(false) + }) + } + + useEffect(() => { + GetFullList(Url('scores', '', '0', 'DESC')) + .then( + (result) => { + if (result.state) { + setIsLoaded(true); + setItems(result.data.concat(AddOneLenght)); + } else setError(true); + + }, + ) + }, []); + + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else { + + //Words in the top part of the table + const topTable = ['ID', 'NOME', 'CÓDIGO', 'PESO', 'ATIVO', 'SCORE TYPE', 'VISUALIZAR']; + + return ( + <div> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <Paper style={{ padding: '1em' }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4"> + Variáveis de nota + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('scores', '', `${currPage}`, 'DESC')) + }} + startIcon={<UpdateRoundedIcon />} + > + { + isUpdating ? <CircularProgress /> : 'Atualizar' + } + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + </Paper> + + <div style={{ height: '2em' }}></div> + + <TableData + top={topTable} + onIconPressed={InvertList} + > + <TableBody> + {items.map((row, index) => ( + index === items.length - 1 ? + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color='primary' + variant='text' + disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('scores', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('scores', '', `${currPage}`, 'DESC')) + } + }} + > + { + isLoadingMoreItems ? <CircularProgress /> : 'Carregar mais itens' + } + </Button> + </StyledTableCell> + </StyledTableRow> + + : + + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row">{row.id}</StyledTableCell> + <StyledTableCell align="right">{row.name}</StyledTableCell> + <StyledTableCell align="right">{row.code}</StyledTableCell> + <StyledTableCell align="right">{row.weight}</StyledTableCell> + <StyledTableCell align="right"> + { + row.active ? <CheckRoundedIcon style={{ fill: '#3CB371' }} /> : <BlockRoundedIcon style={{ fill: '#FA8072' }} /> + } + </StyledTableCell> + <StyledTableCell align="right"> + { + row['score_type'].map((item) => ( + <Typography key={item} style={{ fontSize: 14 }}> + {item} + </Typography> + )) + } + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/noteVar/${row.id}`}> + <IconButton> + <VisibilityIcon style={{ fill: '#00bcd4' }} /> + </IconButton> + </Link> + </StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </TableData> + </div> + ) + } +} + + +export default NoteVariables; \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/Questions.js b/src/Admin/Pages/Pages/SubPages/Questions.js new file mode 100644 index 0000000000000000000000000000000000000000..08fcdd013a3adba875b1f4dbb50004fdd7cc626f --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Questions.js @@ -0,0 +1,382 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState, useContext } from 'react' +import moment from 'moment'; +//imports from local files +import TableData from '../../../Components/Components/Table'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import Unauthorized from '../../../Components/Components/Unauthorized'; +import { Url, EditFilter } from '../../../Filters'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +import { GetFullList, Edit } from '../../../Services'; +//imports from material ui +import { withStyles } from '@material-ui/core/styles'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableRow from '@material-ui/core/TableRow'; +import { Button, Typography, Paper, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import UpdateRoundedIcon from '@material-ui/icons/UpdateRounded'; +import CheckCircleRoundedIcon from '@material-ui/icons/CheckCircleRounded'; +import CancelRoundedIcon from '@material-ui/icons/CancelRounded'; +import Switch from '@material-ui/core/Switch'; +//router +import { Link } from 'react-router-dom'; + +let currPage = 0; +let transformListToAsc = false; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Questions = () => { + const { state, dispatch } = useContext(Store); + + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = ['ID', 'CRIAÇÃO EM', 'DESCRIÇÃO', 'STATUS', 'ATUALIZAÇÃO EM'] //Labels from Table + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false) //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true) + GetFullList(api).then(res => { + if (res.state) { + const arrData = [...res.data] + if (arrData.length === 0) { + HandleSnack('Não há mais dados para serem carregados', true, 'warning', '#FFC125') + } else { + const arrItems = [...items] + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData) + setItems(arrResult.concat(ADD_ONE_LENGHT)) + } + + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsLoadingMoreItems(false) + }) + } + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true) + GetFullList(api).then(res => { + if (res.state) { + HandleSnack('A lista de dados foi atualizada', true, 'success', '#228B22') + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsUpdating(false) + }) + } + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + GetFullList(Url('questions', '', `${currPage}`, 'ASC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + GetFullList(Url('questions', '', `${currPage}`, 'DESC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + } + + const handleChange = async (index, status) => { + const id = items[index].id; + const description = items[index].description; + if (status === 'active') { + const body = { + "question": { + "description": description, + "status": "inactive" + } + } + Edit(EditFilter('questions', id), body).then(res => { + if (res) { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('questions', '', `${currPage}`, 'DESC')) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + const body = { + "question": { + "description": description, + "status": "active" + } + } + Edit(EditFilter('questions', id), body).then(res => { + if (res) { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('questions', '', `${currPage}`, 'DESC')) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + + } + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + //getting data from server + useEffect(() => { + GetFullList(Url('questions', '', `${currPage}`, 'DESC')) + .then( + (result) => { + if (result.state) { + setIsLoaded(true); + setItems(result.data.concat(ADD_ONE_LENGHT)); + } else setError(true) + }, + ) + }, []); + + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else if (CheckUserPermission()) { + return ( + <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + + <Paper style={{ padding: '1em' }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4"> + Perguntas da curadoria + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('questions', '', `${currPage}`, 'DESC')) + }} + startIcon={<UpdateRoundedIcon />} + > + { + isUpdating ? <CircularProgress size={24} /> : 'Atualizar' + } + </Button> + </Grid> + <Grid item> + + <Link style={{ textDecoration: 'none' }} to={'/admin/CreateQuestion'}> + <Button + variant="contained" + color="secondary" + startIcon={<AddRoundedIcon />} + onClick={() => { currPage = 0 }} + > + Novo + </Button> + </Link> + </Grid> + </Grid> + </Grid> + </Grid> + </Paper> + + <div style={{ height: '2em' }}></div> + + <TableData + top={TOP_LABELS} + onIconPressed={InvertList} + > + <TableBody> + {items.map((row, index) => ( + index === items.length - 1 ? + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color='primary' + variant='text' + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('questions', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('questions', '', `${currPage}`, 'DESC')) + } + }} + > + { + isLoadingMoreItems ? <CircularProgress size={24} /> : 'Carregar mais itens' + } + </Button> + </StyledTableCell> + </StyledTableRow> + + : + + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row">{row.id}</StyledTableCell> + <StyledTableCell align="right">{DisplayDate(row.created_at)}</StyledTableCell> + <StyledTableCell align="right">{row.description}</StyledTableCell> + <StyledTableCell align="right"> + { + row.status === 'active' ? + <Grid container direction='row'> + <Grid item> + <CheckCircleRoundedIcon style={{ fill: '#3CB371' }} /> + + <Switch + checked={true} + onChange={() => handleChange(index, row.status)} + name="checkedB" + color="primary" + /> + </Grid> + </Grid> + + : + + <Grid container justify='flex-end' alignItems='center' direction='row'> + <Grid item> + <CancelRoundedIcon style={{ fill: '#FA8072' }} /> + </Grid> + + <Grid item> + <Switch + checked={false} + onChange={() => handleChange(index, row.status)} + name="checkedB" + color="primary" + /> + </Grid> + </Grid> + } + </StyledTableCell> + <StyledTableCell align="right">{DisplayDate(row.updated_at)}</StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </TableData> + </> + ); + } else return <Unauthorized /> +} +export default Questions; \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/Rating.js b/src/Admin/Pages/Pages/SubPages/Rating.js new file mode 100644 index 0000000000000000000000000000000000000000..e102e928040d14510b81a978fcaf79359e49e0cc --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Rating.js @@ -0,0 +1,354 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState } from 'react'; +//Material ui componets +import { withStyles } from '@material-ui/core/styles'; +import TableBody from '@material-ui/core/TableBody'; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import TableCell from '@material-ui/core/TableCell'; +import TableRow from '@material-ui/core/TableRow'; +import IconButton from '@material-ui/core/IconButton'; +import VisibilityIcon from '@material-ui/icons/Visibility'; +import { Button, Typography } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import UpdateRoundedIcon from '@material-ui/icons/UpdateRounded'; +import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded'; +//Local files +import TableData from '../../../Components/Components/Table'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//Services +import AlertDialog from "../../../Components/Components/AlertDialog"; +import { Url } from '../../../Filters'; +import { GetFullList, Delete } from '../../../Services'; +import { DeleteFilter } from '../../../Filters'; +//routers +import { Link } from 'react-router-dom'; + +let currPage = 0; //var that controlls the current page that we are +let transformListToAsc = false; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Ratings = () => { + const AddOneLenght = ['']; + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false) + const [isUpdating, setIsUpdating] = useState(false) + + const [openAlertDialog, setOpenAlertDialog] = useState(false); //controlls the state od alert dialog + const [deleteItem, setDeleteItem] = useState({}); //Delete Item + const [isLoadingToDelete, setIsLoadingToDelete] = useState(null); + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true) + GetFullList(api).then(res => { + if (res.state) { + const arrData = [...res.data] + if (arrData.length === 0) { + HandleSnack('Não há mais dados para serem carregados', true, 'warning', '#FFC125') + } else { + const arrItems = [...items] + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData) + setItems(arrResult.concat(AddOneLenght)) + } + + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsLoadingMoreItems(false) + }) + } + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + GetFullList(Url('ratings', '', `${currPage}`, 'ASC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(AddOneLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + GetFullList(Url('ratings', '', `${currPage}`, 'DESC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(AddOneLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + } + + const UpdateHandler = async (api) => { + setIsUpdating(true) + GetFullList(api).then(res => { + if (res.state) { + HandleSnack('A lista de dados foi atualizada', true, 'success', '#228B22') + const arrData = [...res.data] + setItems(arrData.concat(AddOneLenght)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsUpdating(false) + }) + } + + //handle Delete + async function DeleteHandler() { + const id = deleteItem.id; + HandleStateAlertDialog(null); + Delete(DeleteFilter("ratings", id)).then((res) => { + if (res) { + HandleSnack( + "O rating foi deletada com sucesso", + true, + "success", + "#228B22" + ); + currPage = 0; + transformListToAsc = false + UpdateHandler(Url("ratings", "", `${currPage}`, "DESC")); + } else { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + HandleStateCircularProgress(null); + }); + } + + const HandleStateCircularProgress = (i) => { + setIsLoadingToDelete(i); + }; + + const HandleStateAlertDialog = (i) => { + const obj = { ...items[i] }; + setDeleteItem(obj); + setOpenAlertDialog(!openAlertDialog); + }; + + + useEffect(() => { + GetFullList(Url('ratings', '', currPage, 'DESC')) + .then( + (result) => { + if (result.state) { + setIsLoaded(true); + setItems(result.data.concat(AddOneLenght)); + } else setError(true) + }, + ) + }, []); + + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else { + + //Words in the top part of the table + const topTable = ['ID', 'NOME', 'DESCRIÇÃO', 'VISUALIZAR', 'DELETAR']; + + return ( + <div> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <Paper style={{ padding: '1em' }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4"> + Lista de ratings + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('ratings', '', `${currPage}`, 'DESC')) + }} + startIcon={<UpdateRoundedIcon />} + > + { + isUpdating ? <CircularProgress size={24} /> : 'Atualizar' + } + </Button> + </Grid> + <Grid item> + <Link style={{ textDecoration: 'none' }} to={'/admin/CreateRating'}> + <Button + variant="contained" + color="secondary" + startIcon={<AddRoundedIcon style={{ fill: "white" }} />} + onClick={() => { + currPage = 0 + transformListToAsc = false + }} + > + Novo + </Button> + </Link> + </Grid> + </Grid> + </Grid> + </Grid> + </Paper> + + <div style={{ height: '2em' }}></div> + + <TableData + top={topTable} + onIconPressed={InvertList} + > + <TableBody> + {items.map((row, index) => ( + index === items.length - 1 ? + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color='primary' + variant='text' + disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('ratings', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('ratings', '', `${currPage}`, 'DESC')) + } + }} + > + { + isLoadingMoreItems ? <CircularProgress size={24} /> : 'Carregar mais itens' + } + </Button> + </StyledTableCell> + </StyledTableRow> + + : + + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row">{row.id}</StyledTableCell> + <StyledTableCell align="right">{row.name}</StyledTableCell> + <StyledTableCell align="right">{row.description}</StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/Rating/${row.id}`}> + <IconButton> + <VisibilityIcon style={{ fill: '#00bcd4' }} /> + </IconButton> + </Link> + </StyledTableCell> + <StyledTableCell align="right"> + {isLoadingToDelete === index ? ( + <CircularProgress size={24} color="primary" /> + ) : ( + <IconButton + onClick={() => { + HandleStateAlertDialog(index); + HandleStateCircularProgress(index); + }} + > + <DeleteRoundedIcon style={{ fill: "#FF0000" }} /> + </IconButton> + )} + </StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </TableData> + + {/* This alert will be displayed if the user click to delete an institution */} + <AlertDialog + open={openAlertDialog} + OnDelete={DeleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpenAlertDialog(false); + HandleStateCircularProgress(null); + }} + /> + </div> + ) + } +} + + +export default Ratings; \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/SendEmail.js b/src/Admin/Pages/Pages/SubPages/SendEmail.js new file mode 100644 index 0000000000000000000000000000000000000000..e48e5361015dcaaff2a0e84a207078aa8115df90 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/SendEmail.js @@ -0,0 +1,97 @@ + +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + + +import React, {useContext} from 'react'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import { makeStyles } from "@material-ui/core/styles"; +import { Typography } from '@material-ui/core'; +import EmailInputs from '../../../Components/Components/Inputs/EmailInputs'; +import { Store } from '../../../../Store'; +import Unauthorized from '../../../Components/Components/Unauthorized'; + +const useStyles = makeStyles({ + root: { + minWidth: 900, + boxShadow: "2px 2px 1px #A9A9A9", + }, + title: { + fontSize: 28, + fontWeight: "500", + }, + subTitle: { + fontSize: 14, + fontWeight: "500", + }, + pos: { + marginBottom: 12, + }, + displayRow: { + display: "flex", + flexDirection: "row", + justifyContent: 'space-around', + alignItems: "center", + }, + displayColumn: { + display: "flex", + flexDirection: "column", + marginBottom: "1em", + }, +}); + +const SendEmail = ({ match }) => { + const { state, dispatch } = useContext(Store); + const classes = useStyles(); + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true; + } + else { + canUserEdit = false; + } + + return canUserEdit; + } + + if(CheckUserPermission()){ + return ( + <Card> + <CardContent> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Enviar email + </Typography> + <EmailInputs email={`${match.params.email}`} /> + </CardContent> + </Card> + ); + } else return <Unauthorized/> +} + +export default SendEmail; \ No newline at end of file diff --git a/src/Admin/Services.js b/src/Admin/Services.js new file mode 100644 index 0000000000000000000000000000000000000000..bce02eb72a414c7f04cbbdb8e0acbec383ca2fd6 --- /dev/null +++ b/src/Admin/Services.js @@ -0,0 +1,241 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import axios from 'axios'; + +export function Delete(api) { + return new Promise(resolve => { + axios({ + method: 'delete', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + }).then(response => { + if (response.status === 200) { + resolve(true); + } else { + resolve(false); + } + SaveNewHeaders(response) + }).catch(err =>{ + resolve(false) + }) + }) +} + +export function SendEmail(api, body) { + return new Promise(resolve => { + axios({ + method: 'post', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + data: JSON.stringify(body) + }).then(response => { + console.log(response) + if (response.status === 200) { + resolve(true); + } else { + resolve(false); + } + SaveNewHeaders(response) + }).catch(err =>{ + resolve(false) + }) + }) +} + +export function Edit(api, body) { + return new Promise(resolve => { + axios({ + method: 'put', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + data: JSON.stringify(body) + }).then(response => { + console.log(response) + if (response.status === 200) { + resolve(true) + } else { + resolve(false) + } + SaveNewHeaders(response) + }).catch(err =>{ + resolve(false) + }) + }) +} + +export function Create(api, body) { + return new Promise(resolve => { + axios({ + method: 'post', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + data: JSON.stringify(body) + }).then(response => { + if (response.status === 201) { + resolve(true) + } else { + resolve(false) + } + SaveNewHeaders(response) + }).catch(err =>{ + resolve(false) + }) + }) +} + +export function HandleComplain(api) { + return new Promise(resolve => { + axios({ + method: 'post', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + }).then(response => { + if (response.status === 200) { + resolve(true) + } else { + resolve(false) + } + SaveNewHeaders(response) + }).catch(err =>{ + resolve(false) + }) + }) +} + +export function GetFullList(api, headers) { + + return new Promise(resolve => { + axios({ + method: 'get', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + }).then((res) => { + console.log(res) + if (res.status === 200) { + resolve({ + state: true, + data: res.data + }) + } else { + resolve({ + state: false, + data: {} + }) + } + SaveNewHeaders(res) + }).catch((err) => { + resolve({ + state: false, + data: {} + }) + }) + }, []); +} + +export function GetSpecificData(api, headers) { + return new Promise(resolve => { + axios({ + method: 'get', + url: api, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': sessionStorage.getItem('@portalmec/accessToken'), + 'client': sessionStorage.getItem('@portalmec/clientToken'), + 'uid': sessionStorage.getItem('@portalmec/uid'), + 'If-None-Match': null + }, + }).then((res) => { + console.log(res) + if (res.status === 200) { + resolve({ + state: true, + data: res.data + }) + } else { + resolve({ + state: false, + data: {} + }) + } + SaveNewHeaders(res) + }).catch((err) => { + resolve({ + state: false, + data: {} + }) + }) + }, []); +} + +const SaveNewHeaders = (response) => { + if ( + (response.headers['access-token'] === undefined || response.headers['access-token'] === null) && + (response.headers.client === undefined || response.headers.client === null) + ) { + + } else { + sessionStorage.setItem('@portalmec/accessToken', response.headers['access-token']) + sessionStorage.setItem('@portalmec/clientToken', response.headers.client) + console.log('saved') + } +} + diff --git a/src/App.js b/src/App.js index 80523ca988f9386b244f9a4704aab7bb2cc40257..0f4b3a9c863f9c4c522c8fc8fa47cc3ed08174f5 100644 --- a/src/App.js +++ b/src/App.js @@ -23,6 +23,7 @@ import Header from './Components/Header' import EcFooter from './Components/EcFooter'; import GNUAGPLfooter from './Components/AGPLFooter'; import UserPage from './Pages/UserPage'; +import Admin from './Admin/Pages/Pages/Admin'; import UserTerms from './Pages/UserTerms'; import Contact from './Pages/Contact'; import Teste from './Pages/Teste'; @@ -60,31 +61,31 @@ export default function App(){ setHideFooter(String(window.location.href).includes('iframe-colecao')); }, [ window.location.href ]); - useEffect(()=>{ - dispatch({ - type: 'WINDOW_SIZE', - innerWindow: { - width: window.innerWidth, - height: window.innerHeight - } - }) - },[]) + useEffect(() => { + dispatch({ + type: 'WINDOW_SIZE', + innerWindow: { + width: window.innerWidth, + height: window.innerHeight + } + }) + }, []) - useEffect(()=>{ - const setWindowSize = () => { - dispatch({ - type: 'WINDOW_SIZE', - innerWindow: { - width: window.innerWidth, - height: window.innerHeight + useEffect(() => { + const setWindowSize = () => { + dispatch({ + type: 'WINDOW_SIZE', + innerWindow: { + width: window.innerWidth, + height: window.innerHeight + } + }) } - }) - } - window.addEventListener('resize',setWindowSize) + window.addEventListener('resize', setWindowSize) - return () => window.removeEventListener('resize',setWindowSize) - },[window.innerWidth,window.innerHeight]) + return () => window.removeEventListener('resize', setWindowSize) + }, [window.innerWidth, window.innerHeight]) return( <BrowserRouter basename="/react"> @@ -132,3 +133,6 @@ export default function App(){ </BrowserRouter> ) } + + +export default App; \ No newline at end of file diff --git a/src/Components/Header.js b/src/Components/Header.js index feb870da33aa604488bdf521f365bcdc2cac2ce7..7cb8a2db3f7652fef30467738dabfec68c6b8e7f 100644 --- a/src/Components/Header.js +++ b/src/Components/Header.js @@ -37,12 +37,12 @@ function Alert(props) { } -export default function Header(props){ - const { state, dispatch } = useContext(Store) - const [signUpOpen, setSignUp] = useState(false) - const [loginOpen, setLogin] = useState(false) - const [successfulLoginOpen, handleSuccessfulLogin] = useState(false) - const [modalColaborar, setModalColaborar] = useState(false) +export default function Header(props) { + const { state, dispatch } = useContext(Store) + const [signUpOpen, setSignUp] = useState(false) + const [loginOpen, setLogin] = useState(false) + const [successfulLoginOpen, handleSuccessfulLogin] = useState(false) + const [modalColaborar, setModalColaborar] = useState(false) function handleSuccessValidateToken (data) { dispatch ({ @@ -96,23 +96,23 @@ export default function Header(props){ return; } - handleSuccessfulLogin(false); - } + handleSuccessfulLogin(false); + } - const handleSignUp = () => { - setSignUp(!signUpOpen) - } + const handleSignUp = () => { + setSignUp(!signUpOpen) + } - const handleLogin = () => { - setLogin(!loginOpen) - } + const handleLogin = () => { + setLogin(!loginOpen) + } - const handleClickSearch = (open) => { - dispatch({ - type: "HANDLE_SEARCH_BAR", - opened: !state.searchOpen - }) - } + const handleClickSearch = (open) => { + dispatch({ + type: "HANDLE_SEARCH_BAR", + opened: !state.searchOpen + }) + } let windowWidth = window.innerWidth diff --git a/src/Components/MenuList.js b/src/Components/MenuList.js index 643b4b04a72b14cda85a328c8f14ee572a13be83..3614035457caeb857efe57232b3857c7a6ced80f 100644 --- a/src/Components/MenuList.js +++ b/src/Components/MenuList.js @@ -142,94 +142,3 @@ const StyledMenuItem = styled(MenuItem)` color : #a5a5a5 !important; padding : 5px 20px !important; ` - -//import React from 'react'; -//import Button from '@material-ui/core/Button'; -//import ClickAwayListener from '@material-ui/core/ClickAwayListener'; -//import Grow from '@material-ui/core/Grow'; -//import Paper from '@material-ui/core/Paper'; -//import Popper from '@material-ui/core/Popper'; -//import MenuItem from '@material-ui/core/MenuItem'; -//import MenuList from '@material-ui/core/MenuList'; -//import { makeStyles } from '@material-ui/styles'; -//import AccountCircleRoundedIcon from '@material-ui/icons/AccountCircleRounded'; -//import styled from 'styled-components' -//import {BrowserRouter, Switch, Route} from 'react-router-dom'; -// -//const iconStyles = { -// fontSize : "xxx-large", -// color: "white", -// backgroundColor: "gray", -// borderRadius : "50%", -// overflow : "hidden", -// border : "2px solid #fff", -//} -// -//export default function MenuListComposition(props) { -// const [open, setOpen] = React.useState(false); -// const anchorRef = React.useRef(null); -// -// const handleToggle = () => { -// setOpen(prevOpen => !prevOpen); -// }; -// -// const handleClose = event => { -// if (anchorRef.current && anchorRef.current.contains(event.target)) { -// return; -// } -// -// setOpen(false); -// }; -// -// function handleListKeyDown(event) { -// if (event.key === 'Tab') { -// event.preventDefault(); -// setOpen(false); -// } -// } -// -// // return focus to the button when we transitioned from !open -> open -//// const prevOpen = React.useRef(open); -//// React.useEffect(() => { -//// if (prevOpen.current === true && open === false) { -//// anchorRef.current.focus(); -//// } -//// -//// prevOpen.current = open; -//// }, [open]); -// -// return ( -// <div style={{minWidth:"212px"}}> -// <div> -// <Button -// ref={anchorRef} -// aria-controls={open ? 'menu-list-grow' : undefined} -// aria-haspopup="true" -// onMouseOver={handleToggle}> -// <AccountCircleRoundedIcon style={iconStyles}/> -// Minha Ãrea -// </Button> -// <Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal> -// {({ TransitionProps, placement }) => ( -// <Grow -// {...TransitionProps} -// style={{ transformOrigin: placement = 'bottom'}} -// > -// <Paper> -// <ClickAwayListener onClickAway={handleClose}> -// { -// props.items.map((item)=> -// <Route path={item.href} ><MenuItem>{item.name}</MenuItem></Route> -// ) -// } -// </ClickAwayListener> -// </Paper> -// </Grow> -// )} -// </Popper> -// </div> -// </div> -// -// ); -//} -// diff --git a/src/Components/SearchSection.js b/src/Components/SearchSection.js index 228d72b41c9762d19fc62fe0d73676ed36fc643a..c2cdfc4e12bcf56e4d3b19376059d75f84d60c3f 100644 --- a/src/Components/SearchSection.js +++ b/src/Components/SearchSection.js @@ -25,31 +25,31 @@ import {MdInfoOutline} from "react-icons/md" import { FaRegPlayCircle} from "react-icons/fa"; import ModalVideoApresentacao from "./ModalVideoApresentacao.js" -const bannerStyle={ - width: "100%", - backgroundImage: `url(${banner})`, - backgroundSize: "cover", - textAlign: "center" +const bannerStyle = { + width: "100%", + backgroundImage: `url(${banner})`, + backgroundSize: "cover", + textAlign: "center" } -const titleStyle={ - color: "white", - paddingTop: "5%" +const titleStyle = { + color: "white", + paddingTop: "5%" } -const buttonStyle={ - alignItems: "flex-start", - fontSize: "0.7em", - textAlign: "center", - cursor: "pointer", - borderTopLeftRadius: "15px", - borderTopRightRadius: "15px", - lineHeight: 1.42857143, - width: "20%", - marginTop: "1%", - color: "white", - padding: "1.2%", - borderWidth: "5%", - borderStyle: "none", - borderImage: "initial" +const buttonStyle = { + alignItems: "flex-start", + fontSize: "0.7em", + textAlign: "center", + cursor: "pointer", + borderTopLeftRadius: "15px", + borderTopRightRadius: "15px", + lineHeight: 1.42857143, + width: "20%", + marginTop: "1%", + color: "white", + padding: "1.2%", + borderWidth: "5%", + borderStyle: "none", + borderImage: "initial" } class SearchSection extends Component{ diff --git a/src/Components/SnackbarComponent.js b/src/Components/SnackbarComponent.js index 74730d3c4b949a6e4af69f5799ba6bad09961fca..0a359a78ba406c70f94ce37fba45b979f4608f60 100644 --- a/src/Components/SnackbarComponent.js +++ b/src/Components/SnackbarComponent.js @@ -22,10 +22,10 @@ import Snackbar from '@material-ui/core/Snackbar'; export default function SnackbarComponent (props) { return ( - <Snackbar open={props.snackbarOpen} autoHideDuration={1000} onClose={props.handleClose} + <Snackbar open={props.snackbarOpen} autoHideDuration={3000} onClose={props.handleClose} anchorOrigin = {{ vertical:'top', horizontal:'right' }} > - <Alert severity={props.severity} style={{backgroundColor:"#00acc1"}}> + <Alert severity={props.severity} style={ props.color === null ? {backgroundColor:"#00acc1"} : {backgroundColor: props.color}}> {props.text} </Alert> </Snackbar> diff --git a/src/Pages/Home.js b/src/Pages/Home.js index 2ad839baec1eabd782b36dfb2e523637b7d2ad27..9e1ed689888c44d5f3f7ad22d6896f239980ab8d 100755 --- a/src/Pages/Home.js +++ b/src/Pages/Home.js @@ -16,37 +16,37 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import './Styles/Home.css'; import SearchSection from '../Components/SearchSection'; import SubPages from '../Components/AreasSubPages'; import StatsBar from '../Components/StatsBar'; import Funcionalities from '../Components/Funcionalities'; class App extends Component { - constructor(props){ - super(props); - this.state={ - bannerState: "Recursos" - }; - this.changeBanner = this.changeBanner.bind(this) - } - - changeBanner(parameter){ - this.setState({ - bannerState: parameter - }); - } + constructor(props) { + super(props); + this.state = { + bannerState: "Recursos" + }; + this.changeBanner = this.changeBanner.bind(this) + } + + changeBanner(parameter) { + this.setState({ + bannerState: parameter + }); + } render() { - return ( - <React.Fragment> - <SearchSection function={this.changeBanner} banner={this.state.bannerState}/> - <SubPages banner={this.state.bannerState}/> - <StatsBar/> - <Funcionalities/> - </React.Fragment> - ); + return ( + <React.Fragment> + <SearchSection function={this.changeBanner} banner={this.state.bannerState} /> + <SubPages banner={this.state.bannerState} /> + <StatsBar /> + <Funcionalities /> + </React.Fragment> + ); } - } +} export default App; diff --git a/src/Store.js b/src/Store.js index cd077019da28bea447876f15b4c1c8abd3ab8ac8..2cf8d373cb9b4f3f88c18bfd71199b893af0886d 100644 --- a/src/Store.js +++ b/src/Store.js @@ -49,6 +49,8 @@ const initialState = { } } + + function reducer(state, action) { switch (action.type){ case 'SAVE_SEARCH': @@ -169,11 +171,11 @@ function reducer(state, action) { } export function StoreProvider(props) { - const [state, dispatch] = React.useReducer(reducer, initialState); - const value = { state, dispatch }; - return ( - <Store.Provider value={value}> - {props.children} - </Store.Provider> - ) + const [state, dispatch] = React.useReducer(reducer, initialState); + const value = { state, dispatch }; + return ( + <Store.Provider value={value}> + {props.children} + </Store.Provider> + ) } diff --git a/src/env.js b/src/env.js index c13c03e40d90e114c527b17dd43088c759948fcb..b7359157e1c0e9d260976bbe2b2545a2e0ca51b0 100644 --- a/src/env.js +++ b/src/env.js @@ -25,6 +25,7 @@ var simcaqAPIDomain = 'https://www.simcaq.c3sl.ufpr.br/api', apiSimcaqVersion = 'v1', simcaqAPIurl = simcaqAPIDomain + '/' + apiSimcaqVersion + export {apiUrl}; export {apiDomain}; export {simcaqAPIurl} diff --git a/src/index.css b/src/index.css index 4e83f283d3889e3eed001d2fa220e4b5f0b009d9..954a9e8f75d3224cd71757bc80193fdaad5ac914 100755 --- a/src/index.css +++ b/src/index.css @@ -30,3 +30,7 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +a { + text-decoration: none; +} diff --git a/tsconfig.json b/tsconfig.json index 0980b23fa1e05a97ba9ea750849ebc32ab890875..ce12e006e2adffd753e4462829f288ad87393ca0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, + "experimentalDecorators" : true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext",