diff --git a/Brewfile b/Brewfile new file mode 100644 index 0000000000..7562594f48 --- /dev/null +++ b/Brewfile @@ -0,0 +1,7 @@ +tap "caskroom/cask" + +cask "google-chrome" +cask "firefox" + +brew "chromedriver" +brew "geckodriver" diff --git a/Gemfile b/Gemfile index 50771662fa..35902538e0 100644 --- a/Gemfile +++ b/Gemfile @@ -70,3 +70,6 @@ group :test do gem "capybara" gem "selenium-webdriver" end + +gem "activerecord-import" + diff --git a/Gemfile.lock b/Gemfile.lock index f229958343..7208ffc91c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,6 +56,8 @@ GEM activemodel (= 8.1.3) activesupport (= 8.1.3) timeout (>= 0.4.0) + activerecord-import (2.2.0) + activerecord (>= 4.2) activestorage (8.1.3) actionpack (= 8.1.3) activejob (= 8.1.3) @@ -83,9 +85,9 @@ GEM bcrypt_pbkdf (1.1.2) bigdecimal (4.1.2) bindex (0.8.1) - bootsnap (1.24.5) + bootsnap (1.24.6) msgpack (~> 1.2) - brakeman (8.0.4) + brakeman (8.0.5) racc builder (3.3.0) bundler-audit (0.9.3) @@ -100,7 +102,7 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - concurrent-ruby (1.3.6) + concurrent-ruby (1.3.7) connection_pool (3.0.2) crass (1.0.6) cucumber (10.2.0) @@ -185,7 +187,7 @@ GEM jbuilder (2.15.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.19.7) + json (2.19.9) kamal (2.11.0) activesupport (>= 7.0) base64 (~> 0.2) @@ -218,9 +220,9 @@ GEM minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) - msgpack (1.8.1) + msgpack (1.8.3) multi_test (1.1.0) - net-imap (0.6.4) + net-imap (0.6.4.1) date net-protocol net-pop (0.1.2) @@ -262,7 +264,7 @@ GEM actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack - psych (5.3.1) + psych (5.4.0) date stringio public_suffix (7.0.5) @@ -335,7 +337,7 @@ GEM rspec-mocks (>= 3.13.0, < 5.0.0) rspec-support (>= 3.13.0, < 5.0.0) rspec-support (3.13.7) - rubocop (1.87.0) + rubocop (1.88.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -353,7 +355,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.35.3) + rubocop-rails (2.35.4) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -367,9 +369,9 @@ GEM ruby-vips (2.3.0) ffi (~> 1.12) logger - rubyzip (3.2.2) + rubyzip (3.4.0) securerandom (0.4.1) - selenium-webdriver (4.43.0) + selenium-webdriver (4.45.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -393,13 +395,13 @@ GEM fugit (~> 1.11) railties (>= 7.1) thor (>= 1.3.1) - sqlite3 (2.9.4-aarch64-linux-gnu) - sqlite3 (2.9.4-aarch64-linux-musl) - sqlite3 (2.9.4-arm-linux-gnu) - sqlite3 (2.9.4-arm-linux-musl) - sqlite3 (2.9.4-arm64-darwin) - sqlite3 (2.9.4-x86_64-linux-gnu) - sqlite3 (2.9.4-x86_64-linux-musl) + sqlite3 (2.9.5-aarch64-linux-gnu) + sqlite3 (2.9.5-aarch64-linux-musl) + sqlite3 (2.9.5-arm-linux-gnu) + sqlite3 (2.9.5-arm-linux-musl) + sqlite3 (2.9.5-arm64-darwin) + sqlite3 (2.9.5-x86_64-linux-gnu) + sqlite3 (2.9.5-x86_64-linux-musl) sshkit (1.25.0) base64 logger @@ -435,7 +437,7 @@ GEM bindex (>= 0.4.0) railties (>= 8.0.0) websocket (1.2.11) - websocket-driver (0.8.0) + websocket-driver (0.8.1) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -455,6 +457,7 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + activerecord-import bcrypt (~> 3.1.7) bootsnap brakeman @@ -497,6 +500,7 @@ CHECKSUMS activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3 activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219 activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab + activerecord-import (2.2.0) sha256=f8ca99b196e50775723d1f1d192c379f656378dc9f5628240992a0d78807fa4b activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af @@ -506,13 +510,13 @@ CHECKSUMS bcrypt_pbkdf (1.1.2) sha256=c2414c23ce66869b3eb9f643d6a3374d8322dfb5078125c82792304c10b94cf6 bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e - bootsnap (1.24.5) sha256=36b677448524d279b470469aabd5dff4a980e3fa4931a0df68da4a500eb1b6c4 - brakeman (8.0.4) sha256=7bf921fa9638544835df9aa7b3e720a9a72c0267f34f92135955edd80d4dcf6f + bootsnap (1.24.6) sha256=c60bab88c70332290f0a2636a288f675299eb4f804a02a3c085b42eca9da164a + brakeman (8.0.5) sha256=03735f9690d3fd4b32d66aacbf0a6d15a84266bdd06b32c05c8ecc8f6021d2be builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f bundler (4.0.12) sha256=7f8b757d28dfb636e7b24fba2344ac6dd13b5b24f4b46d62573d483f211825ac bundler-audit (0.9.3) sha256=81c8766c71e47d0d28a0f98c7eed028539f21a6ea3cd8f685eb6f42333c9b4e9 capybara (3.40.0) sha256=42dba720578ea1ca65fd7a41d163dd368502c191804558f6e0f71b391054aeef - concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab + concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0 connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d cucumber (10.2.0) sha256=fdedbd31ecf40858b60f04853f2aa15c44f5c30bbac29c6a227fa1e7005a8158 @@ -553,7 +557,7 @@ CHECKSUMS io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 jbuilder (2.15.1) sha256=2430bec28fb0cebacb5875b1009cf9d8bc3c303ccb810c4c8b062a4b51457637 - json (2.19.7) sha256=fe432c8639f6efff69f9d73b518a3705d9581ab93156f981ea72806e1e5bcc3e + json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a kamal (2.11.0) sha256=1408864425e0dec7e0a14d712a3b13f614e9f3a425b7661d3f9d287a51d7dd75 language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 @@ -566,9 +570,9 @@ CHECKSUMS mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 - msgpack (1.8.1) sha256=3fef787cd3965fd119c08a22724a56a93ca25008c3421fc15039f603a8b7c86c + msgpack (1.8.3) sha256=8bda4a6428d3244e50d6bd55854d354edbada88a4e1f4f5731a39a0f86bee6a1 multi_test (1.1.0) sha256=e9e550cdd863fb72becfe344aefdcd4cbd26ebf307847f4a6c039a4082324d10 - net-imap (0.6.4) sha256=9a5598c67a3022c284d98430ef1d4948e7dbdb62596f61081ea8ca933270a02b + net-imap (0.6.4.1) sha256=29f0360d75a7efd3539f16ac1957dea5c0a51ddeceb348db4553c3120914ea0d net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 net-scp (4.1.0) sha256=a99b0b92a1e5d360b0de4ffbf2dc0c91531502d3d4f56c28b0139a7c093d1a5d @@ -590,7 +594,7 @@ CHECKSUMS prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 propshaft (1.3.2) sha256=1d56a3e56a92c21bfc29caf07406b5386b00d4c47ddf357cf989a5a234b1389e - psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 + psych (5.4.0) sha256=14f72d69a611af663d7d70e4a7b67d9eb1f3ae9f8d916b478961d5a0075ba5b7 public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 @@ -614,27 +618,29 @@ CHECKSUMS rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c - rubocop (1.87.0) sha256=b9d9ddf55116a513f8ef2c7ae660662d8b49301f118d3f0df61865b33a5c188d + rubocop (1.88.0) sha256=e420ddf1662d0ef34bc8a2910ac4b396a7ddda0b51a708264405241734b08e0b rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 - rubocop-rails (2.35.3) sha256=6edd45410866912b9b2e90ae3aeafd31d576df2bb2a9c9408f1667a50c32c7de + rubocop-rails (2.35.4) sha256=3aeaa325439c89950e8327565682ea794065d08e2ecbbfe95032bfa295a35df5 rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374 - rubyzip (3.2.2) sha256=c0ed99385f0625415c8f05bcae33fe649ed2952894a95ff8b08f26ca57ea5b3c + rubyzip (3.4.0) sha256=6de39bc9eba302b635a476d16c9e16b0872ad24517c2f98f2b3a7ea23caff57b securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 - selenium-webdriver (4.43.0) sha256=a634377b964b701c6ac0a009ce3a08fa34ec1e1e7fe9a6d57e3088d14529a65c + + selenium-webdriver (4.45.0) sha256=ecac65a4df86ac6f7d707e6dcbacaa9c08b6cf2b966babecfb9653c5aa13e2d1 shoulda-matchers (5.3.0) sha256=f6ba863b8752bb5956aaa73b046d5df5ecfbe9a7acb61f31bf853613e0932f86 + solid_cable (4.0.0) sha256=8379680ef6bf36e195eb876a6306ea290f87d5fa10bc4a757bc2a918f83229b5 solid_cache (1.0.10) sha256=bc05a2fb3ac78a6f43cbb5946679cf9db67dd30d22939ededc385cb93e120d41 solid_queue (1.4.0) sha256=e6a18d196f0b27cb6e3c77c5b31258b05fb634f8ed64fb1866ed164047216c2a - sqlite3 (2.9.4-aarch64-linux-gnu) sha256=ecabed721e6eaad54601d2685f09029d90025efc8d931040dc89cb3f8a2080ec - sqlite3 (2.9.4-aarch64-linux-musl) sha256=ffb4255947fb54c8c3eeca97460c9702b40de91ce390455ef7367ca6a3929a31 - sqlite3 (2.9.4-arm-linux-gnu) sha256=9ee2008b9fbec984c3c165b0d7eedd2bd2a415100b761bfa3a4c6fbec9208bf6 - sqlite3 (2.9.4-arm-linux-musl) sha256=8dc1fe4da6977992cd62decf4a93ccf6cc2e124a5e6a340160d52092f70e837a - sqlite3 (2.9.4-arm64-darwin) sha256=1d5aad413a815d236e96d43f05a1acc600b6cd086800770342a3f9c2877499ff - sqlite3 (2.9.4-x86_64-linux-gnu) sha256=537a3eda71b1df1336d0055cbebe55a7317c34870c192c7b6b9d8d0be6871847 - sqlite3 (2.9.4-x86_64-linux-musl) sha256=3fc5e865b4be9a85d998203ef8d0c0fdcb92f20acf34a254346ff8a19088efec + sqlite3 (2.9.5-aarch64-linux-gnu) sha256=78075b6337d3d182c6d2b4691049ed45cd220826160c9ea18946bf6a1de200dc + sqlite3 (2.9.5-aarch64-linux-musl) sha256=18c801185deb4adc01ddb281e8f672a39e3d1729979ca91e39439cd3eac0402d + sqlite3 (2.9.5-arm-linux-gnu) sha256=1bdfca0c7d63998c60b0f4a8e3c8df2d33800ccc4abd2d612eddbbbc92a4c48b + sqlite3 (2.9.5-arm-linux-musl) sha256=bae1109d12b2e9f588455967729b008e1ff4feb7761749df695019c9079913c6 + sqlite3 (2.9.5-arm64-darwin) sha256=d0cf444a70fc9395d513cfbcc1e6719e224aa645314e3824cb0474c721425aa2 + sqlite3 (2.9.5-x86_64-linux-gnu) sha256=233dbcb6714148dd23bc5aeb33e8efd6eac974969564ddd5794c23d5f52b231e + sqlite3 (2.9.5-x86_64-linux-musl) sha256=e7d3a7474e8af0f96150c21abc203fbab5437206bfcdf11deab7741c0ca516f2 sshkit (1.25.0) sha256=c8c6543cdb60f91f1d277306d585dd11b6a064cb44eab0972827e4311ff96744 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 @@ -654,7 +660,7 @@ CHECKSUMS useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4 websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737 - websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 + websocket-driver (0.8.1) sha256=5ab238238ce230e5d4b262d2be39624c867914eab99171dc4952b58b577c2d96 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12 diff --git a/app/assets/stylesheets/admin.css b/app/assets/stylesheets/admin.css new file mode 100644 index 0000000000..bdb7c3ad32 --- /dev/null +++ b/app/assets/stylesheets/admin.css @@ -0,0 +1,152 @@ +*{ + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Poppins', sans-serif; +} +/* +.header_admin{ + color: yellow; + width: 100%; + height: 0; + background-color: yellow; + top:0; + + + padding: 40px 0 10px 0; +} +*/ + +body{ + height: 100vh; +} + +/*menu principal*/ +nav.navbar{ + width: 80px; + height: 100%; + background-color: #6f2477; + + padding: 40px 0 40px 1%; + + position:fixed; + top:0; + left:0; + + overflow: hidden; + transition: 0.7s; + +} + +nav.navbar.expandir{ + width: 300px; +} + +/*conteudo*/ + +.dashboard-content{ + + width: 100%; + height: 100%; + background-color: #fff; + + padding: 40px 10px 0 8%; + +} + +.dashboard-content{ + transition: 0.7s; +} + +.conteudo-admin{ + color: #000; +} + +.dashboard-content.expandir{ + padding-left: 320px; + + transition: .5s; +} + +/*seções*/ +section{ + overflow: hidden; +} + + +/*botão*/ +.btn-expandir{ + width: 100%; + padding-left: 10px; + +} + +.btn-expandir > i{ + color: #fff; + font-size: 20px; + cursor: pointer; +} + +/* navegação*/ +ul.nav-menu{ + height: 100%; + list-style-type: none; + + padding-top: 10px; +} + +ul li.nav-item{ + transition: 0.2s; + /*margin-left: 10px;*/ +} + +ul li.ativo{ + background-color: #fff; + box-sizing: border-box; + border-end-start-radius: 10px; + border-top-left-radius: 10px; +} + +ul li.nav-item.ativo a{ + color: #6f2477; +} + + +ul li.nav-item:hover{ + background-color: #fff; + box-sizing: border-box; + border-end-start-radius: 10px; + border-top-left-radius: 10px; +} + +ul li.nav-item a:hover{ + color: #6f2477; +} + +ul li.nav-item a{ + color: #fff; + text-decoration: none; + font-size: 20px; + padding: 20px 4%; + + display: flex; + line-height: 40px; + margin-left: 10px; +} + +ul li.nav-item a .nav-text{ + margin-left: 70px; + transition: 0.5s; + opacity:0; + +} + +nav.navbar.expandir .nav-text{ + margin-left: 40px; + opacity: 1; +} + +ul li.nav-item a .nav-icon{ + font-size: 30px; +} + diff --git a/app/assets/stylesheets/admin_gerenciamento.css b/app/assets/stylesheets/admin_gerenciamento.css new file mode 100644 index 0000000000..27e296ea3a --- /dev/null +++ b/app/assets/stylesheets/admin_gerenciamento.css @@ -0,0 +1,59 @@ +.conteudo-admin{ + display: block; + box-sizing: border-box; + justify-content: center; + align-items: center; + min-height: 80vh; + width: 100%; +} + +.btn-group-vertical{ + display: flex; + flex-direction: column; + justify-content: center; + + align-items: center; + + flex-grow: 1; + width: 100%; + padding: 20px; + +} + +.ger-menu{ + padding-top: 10px; + padding-left: 320px; + padding-bottom: 10px; + list-style: none; + width: 100%; +} + + + +ul li.ger-link{ + + width: 100%; + max-width:320px; + padding: 5px; +} + +ul li.ger-link .ger-btn { + display: block; /* Faz o link se comportar como um bloco */ + width: 100%; /* Força o botão a ocupar todos os 320px do pai */ + text-align: center; /* Centraliza o texto dentro do botão */ + color: #000; + text-decoration: none; + font-size: 20px; + padding: 10px; + + background-color: #f8f9fa; + border: 1px solid #ced4da; + border-radius: 8px; + transition: all 0.2s ease-in-out; +} + +ul li.ger-link .ger-btn:hover { + background-color: #e2e6ea; + border-color: #dae0e5; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} \ No newline at end of file diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index c96d3c9362..4426a400e7 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -1,9 +1,60 @@ +require "json" + class DashboardsController < ApplicationController before_action :require_login + + layout "admin_layout", only: [ :admin, :admin_gerenciamento, :admin_avaliacao ] + + # tela de admin def admin end + # tela de gerenciamento + def admin_gerenciamento + @turmas = Turma.all + @discentes = Discente.all + @docentes = Docente.all + end + + # importação do json + def importar_json + # codigo de auxilio + if params["json"].blank? + puts "ERRO: O parâmetro 'json' veio vazio!" + flash[:error] = "Por favor, selecione um arquivo JSON." + redirect_to admin_gerenciamento_dashboard_path and return + end + + puts "Arquivo recebido com sucesso: #{params["json"].original_filename}" + + # Chama o service + resultado = Importer::AcademicDataImporter.import!(params["json"]) + + if resultado + puts "SUCESSO: O Service Object terminou sem erros." + flash[:success] = "JSON importado com sucesso!" + else + puts "AVISO: O Service retornou falso." + end + + redirect_to admin_gerenciamento_dashboard_path + + rescue => e + puts "EXCEÇÃO CAPTURADA NO CONTROLLER: #{e.message}" + puts e.backtrace.first(2) + flash[:error] = "Ocorreu um erro na importação: #{e.message}" + redirect_to admin_gerenciamento_dashboard_path + end + + + + + + # tela de avaliações + def admin_avaliacao + end + def discente end end diff --git a/app/javascript/admin_controller.js b/app/javascript/admin_controller.js new file mode 100644 index 0000000000..3dad599706 --- /dev/null +++ b/app/javascript/admin_controller.js @@ -0,0 +1,61 @@ +function ligarMenuAdmin() { + const menuItems = document.querySelectorAll('.nav-item') + const btnExp = document.querySelector('#btn-exp') + const menuSide = document.querySelector('.navbar') + const mainSide = document.querySelector('.dashboard-content') + + + // MANTÉM O ITEM ATIVO + const urlAtual = window.location.pathname + menuItems.forEach((item) => { + const link = item.querySelector('a') + if (link) { + const href = link.getAttribute('href'); + if (urlAtual === href || (href !== '/' && urlAtual.includes(href))) { + item.classList.add('ativo') + } else if (href === '#gerenciamento' && window.location.hash === '#gerenciamento') { + item.classList.add('ativo') + } else { + item.classList.remove('ativo') + } + } + }) + + // --- AJUSTE: LEMBRAR SE O MENU ESTAVA EXPANDIDO --- + if (menuSide && localStorage.getItem('menuExpandido') === 'true') { + menuSide.classList.add('expandir') + mainSide.classList.add('expandir') + } + + // Lógica original do clique nos itens + function selectLink() { + menuItems.forEach((item) => item.classList.remove('ativo')) + this.classList.add('ativo') + } + + menuItems.forEach((item) => { + item.removeEventListener('click', selectLink) + item.addEventListener('click', selectLink) + }) + + // Lógica original do botão de expandir (com o salvamento da memória) + if (btnExp && menuSide && mainSide) { + function toggleMenu(e) { + e.preventDefault() + menuSide.classList.toggle('expandir') + mainSide.classList.toggle('expandir') + + // Salva no navegador se o menu está aberto (true) ou fechado (false) + const estaExpandido = menuSide.classList.contains('expandir') + const mainExpandido = mainSide.classList.contains('expandir') + localStorage.setItem('menuExpandido', estaExpandido) + localStorage.setItem('menuExpandido', mainExpandido) + } + btnExp.removeEventListener('click', toggleMenu) + btnExp.addEventListener('click', toggleMenu) + } +} + +// Garante o funcionamento com as trocas de página do Turbo +document.addEventListener('turbo:load', ligarMenuAdmin) +ligarMenuAdmin() \ No newline at end of file diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 1156bf8362..d0685d3b7b 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -1,4 +1,8 @@ -// Import and register all your controllers from the importmap via controllers/**/*_controller -import { application } from "controllers/application" -import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" -eagerLoadControllersFrom("controllers", application) +// This file is auto-generated by ./bin/rails stimulus:manifest:update +// Run that command whenever you add a new controller or create them with +// ./bin/rails generate stimulus controllerName + +import { application } from "./application" + +import HelloController from "./hello_controller" +application.register("hello", HelloController) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c8148f..5f345efe04 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" + default from: "ES2026_Grupo01@camaar.com" layout "mailer" end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 0000000000..91b44e54e3 --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,13 @@ +class UserMailer < ApplicationMailer + + def primeiro_acesso(usuario) + @usuario = usuario + + @url_definir_senha = definir_senha_url(email: @usuario.email) + + mail( + to: @usuario.email, + subject: 'Bem-vindo ao CAMAAR! Defina sua senha de acesso' + ) + end +end diff --git a/app/models/turma.rb b/app/models/turma.rb index 5c8c137db0..b489b1b006 100644 --- a/app/models/turma.rb +++ b/app/models/turma.rb @@ -5,3 +5,4 @@ class Turma < ApplicationRecord validates :codigo, presence: true validates :nome, presence: true end + diff --git a/app/services/importer/academic_data_importer.rb b/app/services/importer/academic_data_importer.rb new file mode 100644 index 0000000000..6355c0fd42 --- /dev/null +++ b/app/services/importer/academic_data_importer.rb @@ -0,0 +1,121 @@ +module Importer + class AcademicDataImporter + def self.import!(upload_file) + start_time = Time.current + + file_path = File.read(upload_file.tempfile.path) + data_hash = JSON.parse(file_path) + + + + ActiveRecord::Base.transaction do + unique_turmas = data_hash.uniq { |t| [ t["code"], t["classCode"], t["semester"] ] } + + turma_objects = unique_turmas.map do |t| + Turma.new(codigo: t["code"], turma: t["classCode"], semestre: t["semester"], departamento: "Ciência da Computação") + end + # duplicação de objetos + Turma.import turma_objects, on_duplicate_key_ignore: true, validate: false + + turmas_hash = Turma.all.pluck(:codigo, :turma, :semestre, :id).each_with_object({}) do |(codigo, turma, semestre, id), hash| + hash["#{codigo}-#{turma}-#{semestre}"] = id + end + + # armazenar os discentes e docentes + raw_discentes = [] + raw_docentes = [] + + data_hash.each do |turma_dados| + turma_id = turmas_hash["#{turma_dados['code']}-#{turma_dados['classCode']}-#{turma_dados['semester']}"] + next unless turma_id + + if turma_dados["dicente"].is_a?(Array) + turma_dados["dicente"].each do |d| + raw_discentes << d.merge("turma_id" => turma_id) + end + end + + if turma_dados["docente"].is_a?(Hash) + d_user = turma_dados["docente"] + raw_docentes << d_user.merge("turma_id" => turma_id) + end + end + + + unique_discentes = raw_discentes.uniq { |d| d["matricula"] } + unique_docentes = raw_docentes.uniq { |d| d["email"] } + + discente_objects = unique_discentes.map do |members| + Discente.new( + nome: members["nome"]&.downcase, + curso: members["curso"]&.downcase, + matricula: members["matricula"], + usuario: members["usuario"], + formacao: members["formacao"]&.downcase, + ocupacao: members["ocupacao"]&.downcase, + email: members["email"]&.downcase, + turma_id: members["turma_id"] + ) + end + Discente.import discente_objects, on_duplicate_key_ignore: true, validate: false + + users_discente_objects = unique_discentes.map do |members| + User.new( + nome: members["nome"]&.downcase, + matricula: members["matricula"], + email: members["email"], + perfil: "Discente", + password: "123456", + password_confirmation: "123456", + primeiro_acesso: false + ) + + end + User.import users_discente_objects, on_duplicate_key_ignore: true, validate: false + + emails_importados = users_discente_objects.map(&:email) + usuarios_salvos = User.where(email: emails_importados) + + usuarios_salvos.each do |usuario| + UsuarioMailer.email_primeiro_acesso(usuario).deliver_later + end + + docente_objects = unique_docentes.map do |members| + Docente.new( + nome: members["nome"]&.downcase, + departamento: members["departamento"]&.downcase, + usuario: members["usuario"], + formacao: members["formacao"]&.downcase, + ocupacao: members["ocupacao"]&.downcase, + email: members["email"]&.downcase, + turma: members["turma_id"] + ) + end + Docente.import docente_objects, on_duplicate_key_ignore: true, validate: false + + users_docente_objects = unique_docentes.map do |members| + User.new( + nome: members["nome"]&.downcase, + matricula: members["email"], + email: members["email"], + perfil: "Docente", + password: "123456", + password_confirmation: "123456", + primeiro_acesso: false + ) + end + + User.import users_docente_objects, on_duplicate_key_ignore: true, validate: false + end + # registro de erros + rescue StandardError => e + error = "Erro na transação de importação: #{e.message}" + Rails.logger.error error + raise + + elapsed_time = Time.current - start_time + Rails.logger.info "Tempo total de importação: #{elapsed_time.round(2)} segundos" + true + end + end +end \ No newline at end of file diff --git a/app/views/dashboards/admin.html.erb b/app/views/dashboards/admin.html.erb index f09f6f5f84..0a5cd4512e 100644 --- a/app/views/dashboards/admin.html.erb +++ b/app/views/dashboards/admin.html.erb @@ -1,18 +1,15 @@ -
- -
-

Perfil de Administrador

-

Bem-vindo ao painel do administrador.

-
-
+<% content_for :stylesheet, stylesheet_link_tag("admin", "data-turbo-track": "reload") %> +
+

Perfil de Administrador

+

Bem-vindo ao painel do administrador.

+

Conteúdo

+ Seus testes de criação de downloads entrarão aqui + +
+ diff --git a/app/views/dashboards/admin_avaliacao.html.erb b/app/views/dashboards/admin_avaliacao.html.erb new file mode 100644 index 0000000000..cce4954bd0 --- /dev/null +++ b/app/views/dashboards/admin_avaliacao.html.erb @@ -0,0 +1,9 @@ + +<% content_for :stylesheet, stylesheet_link_tag("admin", "data-turbo-track": "reload") %> + +
+

Avaliações

+

Bem-vindo ao painel do administrador.

+

Conteúdo

+ Seus testes de criação de downloads entrarão aqui +
diff --git a/app/views/dashboards/admin_gerenciamento.html.erb b/app/views/dashboards/admin_gerenciamento.html.erb new file mode 100644 index 0000000000..80ed7b7b65 --- /dev/null +++ b/app/views/dashboards/admin_gerenciamento.html.erb @@ -0,0 +1,44 @@ + + +<% content_for :stylesheet, stylesheet_link_tag("admin", "data-turbo-track": "reload") %> +<% content_for :stylesheet, stylesheet_link_tag("admin_gerenciamento", "data-turbo-track": "reload") %> + +
+

Painel de Gerenciamento

+

Aqui você pode gerenciar as configurações do painel administrativo.

+ +
+ + + + +
+ +
+ <%= hidden_field_tag :authenticity_token, form_authenticity_token %> +
+ + + + +
+
+ +
\ No newline at end of file diff --git a/app/views/layouts/admin_layout.html.erb b/app/views/layouts/admin_layout.html.erb new file mode 100644 index 0000000000..ca2c5d4ae4 --- /dev/null +++ b/app/views/layouts/admin_layout.html.erb @@ -0,0 +1,60 @@ + + + + Painel Administrativo + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= stylesheet_link_tag "admin", "data-turbo-track": "reload" %> + <%= stylesheet_link_tag "admin_gerenciamento", "data-turbo-track": "reload" %> + + <%= javascript_importmap_tags %> + + + + + + + + +
+ <%= yield %> +
+ + <%= javascript_include_tag "admin_controller", "data-turbo-track": "reload" %> + + \ No newline at end of file diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 3aac9002ed..f12f56c770 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -8,6 +8,13 @@ - <%= yield %> +

Olá, <%= @usuario.nome %>!

+ +

Para acessar o sistema CAMAAR defina a sua senha

+ +

+ <%= link_to 'Definir Minha Senha', @url_definir_senha %> +

+ diff --git a/app/views/user_mailer/primeiro_acesso.html.erb b/app/views/user_mailer/primeiro_acesso.html.erb new file mode 100644 index 0000000000..c4c4fe08f6 --- /dev/null +++ b/app/views/user_mailer/primeiro_acesso.html.erb @@ -0,0 +1,5 @@ +

User#primeiro_acesso

+ +

+ <%= @greeting %>, find me in app/views/user_mailer/primeiro_acesso.html.erb +

diff --git a/app/views/user_mailer/primeiro_acesso.text.erb b/app/views/user_mailer/primeiro_acesso.text.erb new file mode 100644 index 0000000000..9b2ce67581 --- /dev/null +++ b/app/views/user_mailer/primeiro_acesso.text.erb @@ -0,0 +1,3 @@ +User#primeiro_acesso + +<%= @greeting %>, find me in app/views/user_mailer/primeiro_acesso.text.erb diff --git a/config/environments/development.rb b/config/environments/development.rb index 4a6ea19c9c..e01f1b671a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -40,6 +40,12 @@ # Make template changes take effect immediately. config.action_mailer.perform_caching = false + # adicionada + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { address: 'localhost', port: 1025 } + config.action_mailer.raise_delivery_errors = false + + # Set localhost to be used by links generated in mailer templates. config.action_mailer.default_url_options = { host: "localhost", port: 3000 } diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 487324424f..68eced570b 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -4,4 +4,4 @@ Rails.application.config.assets.version = "1.0" # Add additional assets to the asset load path. -# Rails.application.config.assets.paths << Emoji.images_path +# Rails.application.config.assets.paths << Emoji.images_path \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 427fc122a4..9d813c8bb2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,11 +8,20 @@ post "/login", to: "sessions#create" delete "/logout", to: "sessions#destroy", as: :logout + get "/admin", to: "dashboards#admin", as: :admin_dashboard + get "/discente", to: "dashboards#discente", as: :discente_dashboard + resources :turmas, only: [:index, :show] + + get "/avaliacao", to: "dashboards#admin_avaliacao", as: :admin_avaliacao_dashboard + get "/gerenciamento", to: "dashboards#admin_gerenciamento", as: :admin_gerenciamento_dashboard + get "/definir_senha", to: "passwords#edit", as: :definir_senha patch "/definir_senha", to: "passwords#update", as: :definir_senha_update - get "/admin", to: "dashboards#admin", as: :admin_dashboard - get "/discente", to: "dashboards#discente", as: :discente_dashboard + # usando o controller existente + # get '/json/importar', to: 'json#importar' + # get '/json/resultados', to: 'json#resultados' + post "admin/importar_json", to: "dashboards#importar_json", as: :importar_json resources :formularios, only: [ :index, :show ] do member do diff --git a/db/migrate/20260604024535_create_users.rb b/db/migrate/20260604024535_create_users.rb index d06ebff5c2..a1500bd554 100644 --- a/db/migrate/20260604024535_create_users.rb +++ b/db/migrate/20260604024535_create_users.rb @@ -7,6 +7,8 @@ def change t.string :perfil, null: false t.string :password_digest, null: false + t.boolean :primeiro_acesso, default: true + t.timestamps end diff --git a/db/migrate/20260611000003_create_turmas.rb b/db/migrate/20260611000003_create_turmas.rb index 1e60ae0fee..324a402ce8 100644 --- a/db/migrate/20260611000003_create_turmas.rb +++ b/db/migrate/20260611000003_create_turmas.rb @@ -4,6 +4,9 @@ def change t.references :departamento, null: false, foreign_key: true t.string :codigo, null: false t.string :nome, null: false + + t.string :turma + t.string :semestre t.timestamps end diff --git a/db/migrate/20260615054518_create_discentes.rb b/db/migrate/20260615054518_create_discentes.rb new file mode 100644 index 0000000000..ab22a43f00 --- /dev/null +++ b/db/migrate/20260615054518_create_discentes.rb @@ -0,0 +1,16 @@ +class CreateDiscentes < ActiveRecord::Migration[8.1] + def change + create_table :discentes do |t| + t.string :nome + t.string :curso + t.string :matricula + t.string :usuario + t.string :formacao + t.string :ocupacao + t.string :email + t.references :turma, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20260615063634_create_docentes.rb b/db/migrate/20260615063634_create_docentes.rb new file mode 100644 index 0000000000..726fe97247 --- /dev/null +++ b/db/migrate/20260615063634_create_docentes.rb @@ -0,0 +1,15 @@ +class CreateDocentes < ActiveRecord::Migration[8.1] + def change + create_table :docentes do |t| + t.string :nome + t.string :departamento + t.string :usuario + t.string :formacao + t.string :ocupacao + t.string :email + t.references :turma, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 491263efa1..966a18701c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,6 +11,7 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema[8.1].define(version: 2026_06_16_231812) do + create_table "admins", force: :cascade do |t| t.datetime "created_at", null: false t.integer "departamento_id", null: false @@ -46,6 +47,55 @@ t.index ["departamento_id"], name: "index_turmas_on_departamento_id" end + + create_table "dado_users", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "curso" + t.string "email" + t.string "formacao" + t.string "matricula" + t.string "nome" + t.string "ocupacao" + t.datetime "updated_at", null: false + t.string "usuario" + end + + create_table "discentes", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "curso" + t.string "email" + t.string "formacao" + t.string "matricula" + t.string "nome" + t.string "ocupacao" + t.integer "turma_id", null: false + t.datetime "updated_at", null: false + t.string "usuario" + t.index ["turma_id"], name: "index_discentes_on_turma_id" + end + + create_table "docentes", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "departamento" + t.string "email" + t.string "formacao" + t.string "nome" + t.string "ocupacao" + t.string "turma" + t.datetime "updated_at", null: false + t.string "usuario" + end + + create_table "turmas", force: :cascade do |t| + t.string "codigo" + t.datetime "created_at", null: false + t.string "semestre" + t.string "turma" + t.datetime "updated_at", null: false + end + + + create_table "users", force: :cascade do |t| t.datetime "created_at", null: false t.string "email", null: false @@ -63,4 +113,6 @@ add_foreign_key "admins", "users" add_foreign_key "templates", "users" add_foreign_key "turmas", "departamentos" + + add_foreign_key "discentes", "turmas" end diff --git a/db/seeds.rb b/db/seeds.rb index 73517a88c9..956ae22715 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -82,7 +82,7 @@ user.perfil = "Discente" user.password = "123456" user.password_confirmation = "123456" - user.primeiro_acesso = false + t.boolean "primeiro_acesso", default: true end User.find_or_create_by!(email: "admin@camaar.com") do |user| diff --git a/features/step_definitions/cadastrar_usuarios_steps.rb b/features/step_definitions/cadastrar_usuarios_steps.rb new file mode 100644 index 0000000000..b6332a46aa --- /dev/null +++ b/features/step_definitions/cadastrar_usuarios_steps.rb @@ -0,0 +1,180 @@ +require 'cgi' + + +# Cenário: [Feliz] Cadastrar usuário com dados válidos +Dado('que eu estou logado no sistema com o perfil de {string}') do |string| + @user_perfil = string.capitalize + + User.find_or_create_by!(email: "admin@camaar.com") do |user| + user.nome = @user_perfil + user.matricula = "000000001" + user.perfil = @user_perfil + user.password = "123456" + user.password_confirmation = "123456" + user.primeiro_acesso = false + end + + visit '/' + + fill_in 'E-mail ou matrícula', with: 'admin@camaar.com' + fill_in 'Senha', with: '123456' + + click_button 'Entrar' + + expect(page).to have_current_path('/admin') + # expect(page).to have_content('Bem-vindo, #{string}') +end + + +Dado('que eu acesso a opção de {string} no menu lateral') do |string| + @opcao_menu = string + expect(page).to have_link(@opcao_menu) # mudar para have_link ou have_button dependendo do tipo de elemento + click_link @opcao_menu +end + + +Quando('eu clico em {string}') do |string| + @import_usuarios = 'Importar Dados' + expect(page).to have_content(@import_usuarios) # mudar para have_link ou have_button dependendo do tipo de elemento + + find('label', text: @import_usuarios).click +end + +Quando('seleciono um arquivo JSON contendo os dados de novos usuários do SIGAA') do + find('label', text: @import_usuarios).click + @arquivo_json = Rails.root.join('class_members.json') + + attach_file('arquivo_json', @arquivo_json, visible: :any) + + click_button('botao_enviar_oculto', visible: false) + + #div_arquivo = find('#secao_importa').trigger('submit') + #expect(page).to have_content(@import_usuarios) # mudar para have_link ou have_button dependendo do tipo de elemento + + #find('label', text: @import_usuarios).click + +end + + +Quando('os dados estão válidos no arquivo importado') do + # Validação pelo registro no banco de dados + expect(Turma.count).to be > 0 + expect(Discente.count).to be > 0 + expect(Docente.count).to be > 0 +end + + +Então('o sistema deve processar o arquivo e criar solicitações de definição de senha para cada usuário novo') do + expect(ActionMailer::Base.deliveries.count).to eq(0) +end + +Então('os usuários devem receber um e-mail com um link para definir suas senhas') do + @discente_user = User.find_or_create_by!(email: "discente@camaar.com") do |user| + user.nome = "Discente teste" + user.matricula = "211025555" + user.perfil = "Discente" + user.password = "123456" + user.password_confirmation = "123456" + user.primeiro_acesso = true + end + expect(ActionMailer::Base.deliveries.count).to be > 0 + @definir_senha_url = "/definir_senha?email=#{CGI.escape(@discente_user.email)}" +end + + +# Cenário: [Feliz] Usuário permanece pendente enquanto não define a senha +Dado('que um usuário foi cadastrado com sucesso no sistema e recebeu um e-mail para definir a senha') do + @discente_user = User.find_or_create_by!(email: "discente@camaar.com") do |user| + user.nome = "Discente teste" + user.matricula = "211025555" + user.perfil = "Discente" + user.password = "123456" + user.password_confirmation = "123456" + user.primeiro_acesso = true + end + expect(@discente_user.primeiro_acesso).to be(true) +end + +Dado('o usuário ainda não definiu sua senha') do + expect(@discente_user.primeiro_acesso).to be(true) +end + +Então('o sistema deve manter o status do usuário como {string}') do |string| + expect(@discente_user.primeiro_acesso).to be(true) +end + +Então('o usuário não deve ter acesso ao sistema até que a senha seja definida') do + expect(@discente_user.reload.primeiro_acesso).to be(true) + +end + + +# Cenário: [Feliz] Usuário define a senha e é ativado +Dado('o usuário definiu sua senha') do + @discente_user = User.find_by(email: "discente@camaar.com") + @discente_user.update!(primeiro_acesso: false, password: "1234567", password_confirmation: "1234567") +end + +Então('o sistema deve atualizar o status do usuário para {string}') do + expect(@discente_user.primeiro_acesso).to be(false) +end + +Então('o usuário deve conseguir acessar o sistema com suas credenciais') do + visit '/' + + fill_in 'E-mail ou matrícula', with: '211025555' + fill_in 'Senha', with: '1234567' + + click_button 'Entrar' +end + + +# Cenário: [Triste] Tentar cadastrar usuário já existente +Dado('que estou logado no sistema com o perfil de {string}') do |string| + @novo_perfil = string + expect(page).to have_current_path('/admin') +end + +Quando('seleciono um arquivo JSON contendo os dados de novos usuários') do + expect(page).to have_link(@opcao_menu) # mudar para have_link ou have_button dependendo do tipo de elemento + click_link @opcao_menu + + @import_usuarios = 'Importar Dados' + expect(page).to have_content(@import_usuarios) # mudar para have_link ou have_button dependendo do tipo de elemento + + find('label', text: @import_usuarios).click + + @arquivo_json = Rails.root.join('class_members.json') + + attach_file('arquivo_json', @arquivo_json, visible: :any) + + click_button('botao_enviar_oculto', visible: false) +end + +Quando('os dados estão inválidos no arquivo importado') do + pending # Write code here that turns the phrase above into concrete actions +end + +Então('o sistema deve exibir uma mensagem de erro {string}') do |string| + erro_msg = string +end + +Então('o sistema não deve criar solicitações de definição de senha para os usuários') do + expect(ActionMailer::Base.deliveries.count).to eq(0) +end + + +# Cenário: [Triste] Tentar cadastrar usuário já existente +Quando('os dados estão válidos mas correspondem a usuários já existentes no sistema') do + expect(Turma.count).to eq(0) + expect(Discente.count).to eq(0) + expect(Docente.count).to eq(0) +end + +Então('o sistema deve exibir uma mensagem de aviso {string}') do |string| + aviso_msg = string +end + +Então('o sistema não deve criar solicitações de definição de senha para os usuários duplicados') do + expect(page).to have_current_path('/admin') +end \ No newline at end of file diff --git a/features/support/chrome.rb b/features/support/chrome.rb new file mode 100644 index 0000000000..7c2800c5f4 --- /dev/null +++ b/features/support/chrome.rb @@ -0,0 +1,39 @@ +require 'capybara/rspec' +require 'selenium/webdriver' +require 'cucumber/rails' + +options = Selenium::WebDriver::Chrome::Options.new +options.add_preference(:download, prompt_for_download: false, + default_directory: '/tmp/downloads') + +options.add_preference(:browser, set_download_behavior: { behavior: 'allow' }) + +Capybara.register_driver :chrome do |app| + Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) +end + +Capybara.register_driver :headless_chrome do |app| + options.add_argument('--headless') + options.add_argument('--disable-gpu') + options.add_argument('--window-size=1280,800') + + driver = Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) + + ### Allow file downloads in Google Chrome when headless!!! + ### https://bugs.chromium.org/p/chromium/issues/detail?id=696481#c89 + bridge = driver.browser.send(:bridge) + + path = '/session/:session_id/chromium/send_command' + path[':session_id'] = bridge.session_id + + bridge.http.call(:post, path, cmd: 'Page.setDownloadBehavior', + params: { + behavior: 'allow', + downloadPath: '/tmp/downloads' + }) + ### + + driver +end + +Capybara.javascript_driver = ENV['GUI'] ? :chrome : :headless_chrome diff --git a/features/support/firefox.rb b/features/support/firefox.rb new file mode 100644 index 0000000000..201e85a7ac --- /dev/null +++ b/features/support/firefox.rb @@ -0,0 +1,18 @@ +require 'capybara/rspec' +require 'selenium/webdriver' +require 'cucumber/rails' + +Capybara.register_driver :firefox do |app| + Capybara::Selenium::Driver.new(app, browser: :firefox) +end + +Capybara.register_driver :headless_firefox do |app| + options = Selenium::WebDriver::Firefox::Options.new + options.headless! # added on https://github.com/SeleniumHQ/selenium/pull/4762 + + Capybara::Selenium::Driver.new app, + browser: :firefox, + options: options +end + +Capybara.javascript_driver = ENV['GUI'] ? :firefox : :headless_firefox diff --git a/features/support/hooks.rb b/features/support/hooks.rb index 49106f9de0..6c07443f96 100644 --- a/features/support/hooks.rb +++ b/features/support/hooks.rb @@ -4,12 +4,14 @@ After do |cenario| if cenario.failed? - nome = cenario.name.gsub(/[^a-z0-9]/i, '_').downcase - caminho = "tmp/screenshots/#{nome}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.png" - if page.respond_to?(:save_screenshot) && Capybara.current_driver != :rack_test - FileUtils.mkdir_p("tmp/screenshots") + begin + nome = cenario.name.gsub(/[^a-z0-9]/i, '_').downcase + caminho = "tmp/screenshots/#{nome}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.png" + FileUtils.mkdir_p('tmp/screenshots') page.save_screenshot(caminho) - attach(File.read(caminho), "image/png") + attach(File.read(caminho), 'image/png') + rescue => e + puts "Erro ao capturar screenshot: #{e.message}" end end end diff --git a/spec/fixtures/user_mailer/primeiro_acesso b/spec/fixtures/user_mailer/primeiro_acesso new file mode 100644 index 0000000000..eaf7278679 --- /dev/null +++ b/spec/fixtures/user_mailer/primeiro_acesso @@ -0,0 +1,3 @@ +UserMailer#primeiro_acesso + +Hi, find me in app/views/user_mailer/primeiro_acesso diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb new file mode 100644 index 0000000000..3e64a10b77 --- /dev/null +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,9 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer_mailer +class UserMailerPreview < ActionMailer::Preview + + # Preview this email at http://localhost:3000/rails/mailers/user_mailer_mailer/primeiro_acesso + def primeiro_acesso + UserMailer.primeiro_acesso + end + +end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb new file mode 100644 index 0000000000..e5cf9fc9f3 --- /dev/null +++ b/spec/mailers/user_mailer_spec.rb @@ -0,0 +1,18 @@ +require "rails_helper" + +RSpec.describe UserMailer, type: :mailer do + describe "primeiro_acesso" do + let(:mail) { UserMailer.primeiro_acesso } + + it "renders the headers" do + expect(mail.subject).to eq("Primeiro acesso") + expect(mail.to).to eq(["to@example.org"]) + expect(mail.from).to eq(["from@example.com"]) + end + + it "renders the body" do + expect(mail.body.encoded).to match("Hi") + end + end + +end diff --git a/spec/models/docente_spec.rb b/spec/models/docente_spec.rb new file mode 100644 index 0000000000..0cd405d9aa --- /dev/null +++ b/spec/models/docente_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Docente, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index de118d15f1..e31b2fe50c 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -18,6 +18,43 @@ RSpec.configure do |config| config.fixture_paths = [ Rails.root.join("spec/fixtures") ] config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + # RSpec.configure do |config| + # ... suas outras configurações ... + + config.before(:each, type: :system) do + # Verifica se a máquina tem Chrome + has_chrome = !`which google-chrome 2>/dev/null || which chrome 2>/dev/null`.empty? + + if has_chrome + # Código para o seu colega (Usa o Chrome normal com interface) + driven_by :selenium, using: :headless_chrome + else + # Código baseado na sua sugestão (Usa o Firefox normal com interface) + driven_by :selenium, using: :headless_firefox + end + end + + # RSpec Rails uses metadata to mix in different behaviours to your tests, + # for example enabling you to call `get` and `post` in request specs. e.g.: + # + # RSpec.describe UsersController, type: :request do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://rspec.info/features/8-0/rspec-rails + # + # You can also infer these behaviours automatically by location, e.g. + # /spec/models would pull in the same behaviour as `type: :model` but this + # behaviour is considered legacy and will be removed in a future version. + # + # To enable this behaviour uncomment the line below. + # config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! config.include FactoryBot::Syntax::Methods diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 80514c2d85..d28b6d6d05 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,20 @@ +require 'capybara/rspec' + +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true diff --git a/spec/system/cadastrar_usuarios_spec.rb b/spec/system/cadastrar_usuarios_spec.rb new file mode 100644 index 0000000000..b672f4e9ea --- /dev/null +++ b/spec/system/cadastrar_usuarios_spec.rb @@ -0,0 +1,121 @@ +require 'rails_helper' + + +RSpec.describe "Cadastrar usuário com dados válidos", type: :system do + before do + # has_firefox = system('which firefox > /dev/null 2>&1 || where firefox > /dev/null 2>&1') + driven_by(:rack_test) + # driven_by(:selenium, using: has_firefox ? :headless_firefox : :headless_chrome) + + @admin = User.create!( + nome: "Administrador", + email: "admin@camaar.com", + matricula: "000000001", + perfil: "Administrador", + password: "123456" + ) + + @arquivo_teste_json = 'class_members.json' + end + + it "logar no perfil de Administrador" do + visit '/login' + + fill_in 'E-mail ou matrícula', with: @admin.email + fill_in 'Senha', with: @admin.password + + click_button 'Entrar' + + visit '/admin' + + expect(page).to have_current_path('/admin') + end + + it "Acessar a opção de Gerenciamento" do + visit '/login' + + fill_in 'E-mail ou matrícula', with: @admin.email + fill_in 'Senha', with: @admin.password + + click_button 'Entrar' + visit '/admin' + + expect(page).to have_link("Gerenciamento") + click_link 'Gerenciamento' + expect(page).to have_content("Painel de Gerenciamento") + end + + it "Clicar na opção de importar dados" do + visit '/login' + + fill_in 'E-mail ou matrícula', with: @admin.email + fill_in 'Senha', with: @admin.password + + click_button 'Entrar' + visit '/admin' + + expect(page).to have_link("Gerenciamento") + click_link 'Gerenciamento' + + @import_usuarios = 'Importar Dados' + expect(page).to have_content(@import_usuarios) # mudar para have_link ou have_button dependendo do tipo de elemento + + find('label', text: @import_usuarios).click + + end + + it "Importar e selecionar o arquivo json" do + visit '/login' + + fill_in 'E-mail ou matrícula', with: @admin.email + fill_in 'Senha', with: @admin.password + + click_button 'Entrar' + visit '/admin' + + click_link 'Gerenciamento' + + @import_usuarios = 'Importar Dados' + expect(page).to have_content(@import_usuarios) # mudar para have_link ou have_button dependendo do tipo de elemento + + find('label', text: @import_usuarios).click + + @arquivo_json = Rails.root.join('class_members.json') + + attach_file('arquivo_json', @arquivo_json, visible: :any) + + click_button('botao_enviar_oculto', visible: false) + + #div_arquivo = find('#secao_importa').trigger('submit') + #expect(page).to_not have_content('Nenhuma turma cadastrada.') + + + end + + it "Dados validos de importação" do + visit '/login' + + fill_in 'E-mail ou matrícula', with: @admin.email + fill_in 'Senha', with: @admin.password + + click_button 'Entrar' + visit '/admin' + + click_link 'Gerenciamento' + + @import_usuarios = 'Importar Dados' + + find('label', text: @import_usuarios).click + + @arquivo_json = Rails.root.join('class_members.json') + attach_file('arquivo_json', @arquivo_json, visible: :any) + click_button('botao_enviar_oculto', visible: false) + + # Validação pelo registro no banco de dados + + expect(Turma.count).to be > 0 + expect(Discente.count).to be > 0 + expect(Docente.count).to be > 0 + end + +end