diff --git a/Gemfile b/Gemfile
index a5e1b962..9c3375b1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -60,7 +60,7 @@ gem 'prawn'
gem 'prawn-qrcode'
gem 'prawn-table'
gem 'rails_12factor', group: :production
-gem 'react_on_rails', '17.0.1'
+gem 'react_on_rails', '17.1.0'
gem 'ruby-vips', '~> 2.0'
gem "solid_cable", "~> 4.0"
gem 'stopwords-filter2', require: 'stopwords'
@@ -98,7 +98,7 @@ group :development, :test do
gem 'report_builder'
gem 'rspec'
gem 'rails_best_practices'
- gem 'rubocop', '~> 1.82', require: false
+ gem 'rubocop', '~> 1.91', require: false
gem 'reek'
gem 'rubocop-thread_safety'
gem 'rubocop-performance'
diff --git a/Gemfile.lock b/Gemfile.lock
index 5ac0ec9d..044defaa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -108,8 +108,8 @@ GEM
auth-sanitizer (0.2.3)
version_gem (~> 1.1, >= 1.1.14)
aws-eventstream (1.4.0)
- aws-partitions (1.1284.0)
- aws-sdk-core (3.255.0)
+ aws-partitions (1.1287.0)
+ aws-sdk-core (3.257.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -118,11 +118,11 @@ GEM
jmespath (~> 1, >= 1.6.1)
logger
rexml (~> 3.4, >= 3.4.2)
- aws-sdk-kms (1.131.0)
- aws-sdk-core (~> 3, >= 3.255.0)
+ aws-sdk-kms (1.132.0)
+ aws-sdk-core (~> 3, >= 3.256.0)
aws-sigv4 (~> 1.5)
- aws-sdk-s3 (1.231.0)
- aws-sdk-core (~> 3, >= 3.255.0)
+ aws-sdk-s3 (1.232.1)
+ aws-sdk-core (~> 3, >= 3.256.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
@@ -262,7 +262,7 @@ GEM
json (>= 2)
faker (3.8.0)
i18n (>= 1.8.11, < 2)
- faraday (2.14.3)
+ faraday (2.14.4)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@@ -289,7 +289,7 @@ GEM
i18n_data (1.1.0)
simple_po_parser (~> 1.1)
image_processing (2.1.0)
- io-console (0.9.2)
+ io-console (0.9.4)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
@@ -300,7 +300,7 @@ GEM
activesupport (>= 7.0.0)
jmespath (1.6.2)
json (3.0.2)
- jwt (3.2.0)
+ jwt (3.3.0)
base64
keypairs (2.0.1)
actionpack (>= 7.1, < 9)
@@ -332,7 +332,7 @@ GEM
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
- msgpack (1.8.4)
+ msgpack (1.8.5)
multi_test (1.1.0)
multi_xml (0.9.1)
bigdecimal (>= 3.1, < 5)
@@ -345,7 +345,7 @@ GEM
net-protocol
net-pop (0.1.2)
net-protocol
- net-protocol (0.3.0)
+ net-protocol (0.4.0)
timeout
net-smtp (0.5.1)
net-protocol
@@ -396,7 +396,7 @@ GEM
ostruct (0.6.3)
package_json (0.2.0)
parallel (2.2.0)
- parallel_tests (5.7.0)
+ parallel_tests (5.8.0)
parallel
parser (3.3.12.0)
ast (~> 2.4.1)
@@ -424,7 +424,7 @@ GEM
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
- rack-proxy (2.0.0)
+ rack-proxy (2.0.1)
rack (>= 2.0, < 4)
rack-session (2.1.2)
base64 (>= 0.1.0)
@@ -496,7 +496,7 @@ GEM
prism (>= 1.6.0)
rbs (>= 4.0.0)
tsort
- react_on_rails (17.0.1)
+ react_on_rails (17.1.0)
addressable
connection_pool
execjs (~> 2.5)
@@ -536,7 +536,7 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.7)
- rubocop (1.90.0)
+ rubocop (1.91.0)
json (>= 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -563,9 +563,9 @@ GEM
rack (>= 1.1)
rubocop (>= 1.89.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
- rubocop-thread_safety (0.7.3)
+ rubocop-thread_safety (0.8.0)
lint_roller (~> 1.1)
- rubocop (~> 1.72, >= 1.72.1)
+ rubocop (~> 1.89)
rubocop-ast (>= 1.44.0, < 2.0)
ruby-lsp (0.26.11)
language_server-protocol (~> 3.17.0)
@@ -577,7 +577,7 @@ GEM
ruby-vips (2.3.0)
ffi (~> 1.12)
logger
- rubyzip (3.6.0)
+ rubyzip (3.7.0)
safely_block (1.0.0)
sdoc (2.6.5)
rdoc (>= 5.0)
@@ -622,7 +622,7 @@ GEM
bigdecimal (~> 3.1)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
- tzinfo-data (1.2026.3)
+ tzinfo-data (1.2026.4)
tzinfo (>= 1.0.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
@@ -708,11 +708,11 @@ DEPENDENCIES
rails-erd
rails_12factor
rails_best_practices
- react_on_rails (= 17.0.1)
+ react_on_rails (= 17.1.0)
reek
report_builder
rspec
- rubocop (~> 1.82)
+ rubocop (~> 1.91)
rubocop-capybara
rubocop-performance
rubocop-rails
@@ -753,10 +753,10 @@ CHECKSUMS
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
auth-sanitizer (0.2.3) sha256=db10aac92cfbe4c64ab637eebcbe1d67395d1694798041362173370f59933e3c
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
- aws-partitions (1.1284.0) sha256=026432da13da430a31ba7c30c0c210b35fa7d738399977f033d4a5a354de58dc
- aws-sdk-core (3.255.0) sha256=2bac7fbc8796e4e2eb8e6a6edebcb880d7023a922af97b15d2a8a26c9283343f
- aws-sdk-kms (1.131.0) sha256=b60d28045cd93c604142cb691b15c7ddc1e6738c4ee5a90db4f2b91f0ada1d15
- aws-sdk-s3 (1.231.0) sha256=a9fc98c6f03f0e71c7215d48ff8844d436421df7f9486301d56bdf1f368a3364
+ aws-partitions (1.1287.0) sha256=76f4c73011654553566c5db5bbef0f24738bb016e923910e8b1b3f9cb7d989ca
+ aws-sdk-core (3.257.0) sha256=a92ad19c7ff668cec6688ee3200ee562da80647a3bbeed9fda02e8b0ca6492c6
+ aws-sdk-kms (1.132.0) sha256=094b0097bb3be9d5c1ac87e971ca7f5aebb746801dfb5f0f576480bf98dca69c
+ aws-sdk-s3 (1.232.1) sha256=124440f3a44fbc28cc3a2dec6eaeeea2a236bfc33772005010a19baa62654870
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
@@ -814,7 +814,7 @@ CHECKSUMS
erubis (2.7.0) sha256=63653f5174a7997f6f1d6f465fbe1494dcc4bdab1fb8e635f6216989fb1148ba
execjs (2.10.2) sha256=466cf2b49e50c0cfdff8fdb88a7a983dfab7ac0f6185c317ccb434a75c224c88
faker (3.8.0) sha256=c147b308df73a90f27a4fc84f18d4c22ef0ad9c2a64b2b61c86fd0ca71753efc
- faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
+ faraday (2.14.4) sha256=9bb4408c44621b0dbaaaa39e5dbfc4dbe58cc6751b6eb94d16ae9e1f2f2fab6e
faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
@@ -833,12 +833,12 @@ CHECKSUMS
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
i18n_data (1.1.0) sha256=fb7ae43bd03ec4dc2e6e62e726aac05aa868c170ba1444c056e2ab0d872bb2ed
image_processing (2.1.0) sha256=ca3814b1b909fc5fab68b261c50eaffef11e29a6907143e9cf1efc14b6dc6f47
- io-console (0.9.2) sha256=efa74f891dd03c0939a931dfc6e74c2813d904763d456ea9762b0525e748db08
+ io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
jbuilder (2.15.1) sha256=2430bec28fb0cebacb5875b1009cf9d8bc3c303ccb810c4c8b062a4b51457637
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df
- jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
+ jwt (3.3.0) sha256=44cc34fbc341c148233cf66641522b4bce0dfb1ce55e8073f05c56f44f4ee546
keypairs (2.0.1) sha256=032e2a8cc2009c578100b5537ad642f2c50d3a0b00d4bc433a2d1a039f80ed91
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
@@ -853,14 +853,14 @@ CHECKSUMS
memoist3 (1.0.0) sha256=686e42402cf150a362050c23143dc57b0ef88f8c344943ff8b7845792b50d56f
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
- msgpack (1.8.4) sha256=4411c22d350dd1c20250f7eada3cca2695438c2f769cf0782f0cd065d90a3e7b
+ msgpack (1.8.5) sha256=97419eaa7b35dfe3e462c0e6c1a3dfdcbfc8756605c09e46e4c6daef127517ba
multi_test (1.1.0) sha256=e9e550cdd863fb72becfe344aefdcd4cbd26ebf307847f4a6c039a4082324d10
multi_xml (0.9.1) sha256=7ce766b59c17241ed62976caeae1fae9b2431b263398c35396239a68c4a64e57
mysql2 (0.5.7) sha256=ba09ede515a0ae8a7192040a1b778c0fb0f025fa5877e9be895cd325fa5e9d7b
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
net-imap (0.6.7) sha256=b5c9573be975d856de252ee851871724da66aa2d449b2482d5690bd12bc23660
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
- net-protocol (0.3.0) sha256=ba310c3d4f1cad46bb1ab20336b06669b1ff8f7c568d9cb9342b32a718547472
+ net-protocol (0.4.0) sha256=f9f90c8c6f64794a45160bf422177a336db12809de3beeeaa26b88fcd2e8c853
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
@@ -880,7 +880,7 @@ CHECKSUMS
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
package_json (0.2.0) sha256=92c022dc2999a9e57e835f1ec1a84c9a6c2be08e0fd68886c6f86d94101b6b4d
parallel (2.2.0) sha256=e1059c5fd7b649558a0aec38a769f06a42942bdb40503d005a59c352fe011cd8
- parallel_tests (5.7.0) sha256=3f1762c46ca2c223b8af8ef877217f9d76974e191bfa934f2580b58bcf1d005c
+ parallel_tests (5.8.0) sha256=181898cbee2ffb1c796274ce31d27f792a421324609bfc7e9b5531d929d91eda
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
pdf-core (0.10.0) sha256=0a5d101e2063c01e3f941e1ee47cbb97f1adfc1395b58372f4f65f1300f3ce91
powerpoint (1.8)
@@ -895,7 +895,7 @@ CHECKSUMS
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac
- rack-proxy (2.0.0) sha256=4f1d435d82afe93bc916d1226df8be307c1b808551f0ecdb56e0b668fd5756e6
+ rack-proxy (2.0.1) sha256=e7afa56a48728c22ded1b7c435210918d7b07bdbc61ca8f9fd687c48951ae274
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
@@ -915,7 +915,7 @@ CHECKSUMS
rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e
rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674
rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
- react_on_rails (17.0.1) sha256=fec671b8f93201892f0dd32ab0cc34961eb3de4d1d4a4f273b817eb701277b64
+ react_on_rails (17.1.0) sha256=3c8914dbe61e862932f9dc08c0adb55dac049e0f923fc9a753f6c2beaf279767
reek (6.5.0) sha256=d26d3a492773b2bbc228888067a21afe33ac07954a17dbd64cdeae42c4c69be1
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
@@ -930,17 +930,17 @@ CHECKSUMS
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
- rubocop (1.90.0) sha256=9eb4c065b5c5154e4ef554c547972f3905a9eb6b53e657e580b6796b54bf8242
+ rubocop (1.91.0) sha256=9c82b7bf391c5d7e3798c5b9996e22a1fe3bd7468e351dfdeb96140c058296d0
rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
rubocop-capybara (3.0.0) sha256=7a64655238acda7f8f3c87e37ac825a64c615a79c17c253f1a28270dc3768c4b
rubocop-performance (1.27.0) sha256=eeeb1374d062a368ee1c787b70eb0b0cc4b184cb1f8565f424760946146d61ce
rubocop-rails (2.37.0) sha256=6e1645add5060e0328f8ddda0d820f55697c591394398bf14bb9dccb62f14b7e
- rubocop-thread_safety (0.7.3) sha256=067cdd52fbf5deffc18995437e45b5194236eaff4f71de3375a1f6052e48f431
+ rubocop-thread_safety (0.8.0) sha256=71fe5e7894fb9acfbd4c2dcd394feabc35bd690f75b0bc846e1504d9e31f9034
ruby-lsp (0.26.11) sha256=4cc8f1587237ff8c9031680b5491df901a82559eeaa1ae96863ffe700b32583d
ruby-lsp-rails (0.4.8) sha256=f09d1f926d4063deeb2f3049311925c20dfe6c912371e3bcd04a265a865c44ae
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374
- rubyzip (3.6.0) sha256=268994d44d62282d1cfd99bf10eae48d7267199158ad7ea3e1fee2da9458b695
+ rubyzip (3.7.0) sha256=65c19294da75297a939006f3516deacc33185fbd721ff1954f7a231db6d3e121
safely_block (1.0.0) sha256=bea80e084e620adeada62fd98cf461bbb9888d40dc0f06d337df9d01e1a658e5
sdoc (2.6.5) sha256=7998b61c10775ee3179ccb0253774332d139e2c0649c251091f5868107a09b86
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
@@ -962,7 +962,7 @@ CHECKSUMS
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
ttfunk (1.8.0) sha256=a7cbc7e489cc46e979dde04d34b5b9e4f5c8f1ee5fc6b1a7be39b829919d20ca
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
- tzinfo-data (1.2026.3) sha256=478fbc5356f13c1004cf8372b1336f3dad4055c96340fc4c881a3738da8cf7f9
+ tzinfo-data (1.2026.4) sha256=22f5f02608f14938ab8b78513a70a22f0b0277614a07273d3bedb3ea45ece104
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
uniform_notifier (1.18.0) sha256=4787785556f66f6418486da0f1d78b3239aaff98e2e7938fb05e2062b0ffce9d
diff --git a/app/javascript/components/ActivityLib.tsx b/app/javascript/components/ActivityLib.tsx
index c672e1ac..7706b415 100644
--- a/app/javascript/components/ActivityLib.tsx
+++ b/app/javascript/components/ActivityLib.tsx
@@ -9,95 +9,95 @@ export function iconForType(type: string) {
switch (type.toLowerCase()) {
case "home":
iconData.className = "home";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "welcome":
iconData.className = "welcome";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "profile":
iconData.className = "profile";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "perspective":
iconData.className = "perspective";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "concept":
case "concepts":
iconData.className = "concept";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "group experience":
case "experience":
case "experiences":
iconData.className = "experience";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "project":
case "assessment":
case "assessments":
iconData.className = "assessment";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "terms list":
case "bingo_game":
case "bingo games":
iconData.className = "bingo_game";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "group assignment":
case "assignment":
case "assignments":
iconData.className = "assignment";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "submission":
iconData.className = "submission";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "rubric":
case "rubrics":
iconData.className = "rubric";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "course":
case "courses":
iconData.className = "course";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "user":
case "users":
iconData.className = "user";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "reporting":
iconData.className = "reporting";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "administration":
case "admin":
iconData.className = "admin";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "school":
case "schools":
iconData.className = "school";
- iconData.icon = ;
+ iconData.icon = ;
break;
case "consent_form":
case "consent_forms":
iconData.className = "consent_form";
- iconData.icon = ;
+ iconData.icon = ;
break;
case 'demo':
case 'demonstration':
iconData.className = "demonstration";
- iconData.icon = ;
+ iconData.icon = ;
break;
default:
iconData.className = `unit-${type}`;
- iconData.icon = ;
+ iconData.icon = ;
}
return (
<>
diff --git a/app/javascript/components/AppStatusBar.tsx b/app/javascript/components/AppStatusBar.tsx
index 4841c46e..490cc0d4 100644
--- a/app/javascript/components/AppStatusBar.tsx
+++ b/app/javascript/components/AppStatusBar.tsx
@@ -6,26 +6,38 @@ import { acknowledgeMsg } from "./infrastructure/StatusSlice";
import { Toast } from "primereact/toast";
-export default function AppStatusBar(props) {
- const messages = useTypedSelector(state => {
- return state.status.messages;
+type AppMessage = {
+ text: string;
+ priority: "error" | "info" | "warning";
+ dismissed: boolean;
+};
+
+export default function AppStatusBar() {
+ const messages = useTypedSelector((state): AppMessage[] => {
+ return state.status.messages ?? [];
});
const dispatch = useDispatch();
- const toast = React.useRef(null);
+ const toast = React.useRef(null);
useEffect(() => {
- messages.forEach((message, index) => {
+ messages.forEach((message: AppMessage, index: number) => {
if (!message.dismissed) {
- toast.current.show({
- severity: message.priority,
- summary: message.priority,
- detail: message.text,
- life: 30000
- });
+ if (toast.current) {
+ toast.current.show({
+ severity: message.priority,
+ summary: message.priority,
+ detail: message.text,
+ life: 30000
+ });
+ }
dispatch(acknowledgeMsg(index));
}
});
- }, [messages]);
+ }, [dispatch, messages]);
- return ;
+ return (
+ <>
+
+ >
+ );
}
diff --git a/app/javascript/components/BingoBoards/BingoGameDataAdmin.tsx b/app/javascript/components/BingoBoards/BingoGameDataAdmin.tsx
index df371b99..1ab908f1 100644
--- a/app/javascript/components/BingoBoards/BingoGameDataAdmin.tsx
+++ b/app/javascript/components/BingoBoards/BingoGameDataAdmin.tsx
@@ -1,4 +1,4 @@
-import React, { Suspense, useState, useEffect, useMemo } from "react";
+import React, { Suspense, useState, useEffect, useMemo, useRef } from "react";
import { useNavigate, useParams } from "react-router";
import { useDispatch } from "react-redux";
@@ -10,7 +10,7 @@ import { Button } from "primereact/button";
import { useTranslation } from "react-i18next";
import { useTypedSelector } from "../infrastructure/AppReducers";
-import { startTask, endTask } from "../infrastructure/StatusSlice";
+import { startTask, endTask, useDirtyStatus, DIRTY_STATUS } from "../infrastructure/StatusSlice";
import axios from "axios";
import { Editor } from "primereact/editor";
import EditorToolbar from "../toolbars/EditorToolbar";
@@ -41,7 +41,8 @@ export default function BingoGameDataAdmin(props) {
const { t, i18n } = useTranslation(`${category}s`);
- const [dirty, setDirty] = useState(false);
+ const [dirty, setDirty] = useDirtyStatus( bingoGameIdParam === "new" ? DIRTY_STATUS.DIRTY : DIRTY_STATUS.CLEAN );
+ const suppressDirtyRef = useRef(false);
const [curTab, setCurTab] = useState(0);
const [messages, setMessages] = useState({});
const [gameProjects, setGameProjects] = useState([
@@ -85,7 +86,10 @@ export default function BingoGameDataAdmin(props) {
}, [endpointStatus]);
useEffect(() => {
- setDirty(true);
+ if (suppressDirtyRef.current || null === bingoGameId ) {
+ return;
+ }
+ setDirty(DIRTY_STATUS.DIRTY);
}, [
gameTopic,
gameDescriptionEditor,
@@ -161,9 +165,8 @@ export default function BingoGameDataAdmin(props) {
setGameGroupDiscount(bingo_game.group_discount || 0);
setGameGroupProjectId(bingo_game.project_id);
setFoundWords(data.found_words);
+ setDirty(DIRTY_STATUS.CLEAN);
- //getBingoGameData();
- //setDirty(false);
navigate(`../${courseIdParam}/bingo_game/${bingoGameId}`, {
replace: true
});
@@ -173,6 +176,7 @@ export default function BingoGameDataAdmin(props) {
})
.finally(() => {
dispatch(endTask("saving"));
+ suppressDirtyRef.current = false;
});
};
@@ -200,7 +204,7 @@ export default function BingoGameDataAdmin(props) {
};
const getBingoGameData = () => {
- setDirty(true);
+ suppressDirtyRef.current = true;
dispatch(startTask());
var url = endpoints.baseUrl + "/";
if (null === bingoGameId) {
@@ -240,13 +244,14 @@ export default function BingoGameDataAdmin(props) {
setGameGroupDiscount(bingo_game.group_discount || 0);
setGameGroupProjectId(bingo_game.project_id);
setFoundWords(data.found_words);
- setDirty(false);
+ setDirty(DIRTY_STATUS.CLEAN);
})
.catch(error => {
console.log("error", error);
return [{ id: -1, name: "no data" }];
})
.finally(() => {
+ suppressDirtyRef.current = false;
dispatch(endTask());
});
};
@@ -258,11 +263,11 @@ export default function BingoGameDataAdmin(props) {
return deadline;
}, [gameEndDate, gameLeadTime]);
- const save_btn = dirty ? (
+ const save_btn = (
}>
- ) : null;
+ );
const group_options = gameGroupOption ? (
}>
diff --git a/app/javascript/components/BingoBoards/CandidateListEntry.tsx b/app/javascript/components/BingoBoards/CandidateListEntry.tsx
index 5bd70f65..09fb9794 100644
--- a/app/javascript/components/BingoBoards/CandidateListEntry.tsx
+++ b/app/javascript/components/BingoBoards/CandidateListEntry.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useRef } from "react";
import { useParams } from "react-router";
import { Panel } from "primereact/panel";
@@ -10,7 +10,9 @@ import {
startTask,
endTask,
addMessage,
- Priorities
+ Priorities,
+ useDirtyStatus,
+ DIRTY_STATUS
} from "../infrastructure/StatusSlice";
import { useTypedSelector } from "../infrastructure/AppReducers";
import axios from "axios";
@@ -38,7 +40,8 @@ export default function CandidateListEntry(props: Props) {
const { bingoGameId } = useParams();
- const [dirty, setDirty] = useState(false);
+ const [dirty, setDirty] = useDirtyStatus();
+ const suppressDirtyRef = useRef(false);
const dispatch = useDispatch();
const [candidateListId, setCandidateListId] = useState(0);
@@ -56,8 +59,8 @@ export default function CandidateListEntry(props: Props) {
const [requestCollaborationUrl, setRequestCollaborationUrl] = useState("");
const getCandidateList = () => {
+ suppressDirtyRef.current = true;
dispatch(startTask());
- setDirty(true);
const url =
props.rootPath === undefined
? `${endpoints.baseUrl}${bingoGameId}.json`
@@ -81,7 +84,7 @@ export default function CandidateListEntry(props: Props) {
setHelpRequested(data.help_requested);
setRequestCollaborationUrl(data.request_collaboration_url);
- setDirty(false);
+ setDirty(DIRTY_STATUS.CLEAN);
})
.catch(error => {
console.log("error", error);
@@ -159,7 +162,7 @@ export default function CandidateListEntry(props: Props) {
setHelpRequested(data.help_requested);
setOthersRequestedHelp(data.others_requested_help);
- setDirty(false);
+ setDirty(DIRTY_STATUS.CLEAN);
dispatch(addMessage(data.messages.main, new Date(), Priorities.INFO));
} else {
data.messages.forEach(message => {
@@ -182,7 +185,11 @@ export default function CandidateListEntry(props: Props) {
}, [endpointStatus]);
useEffect(() => {
- setDirty(true);
+ if (suppressDirtyRef.current) {
+ suppressDirtyRef.current = false;
+ return;
+ }
+ setDirty(DIRTY_STATUS.DIRTY);
}, [candidates]);
// TODO: Fix the check to see if the form is dirty
@@ -209,7 +216,7 @@ export default function CandidateListEntry(props: Props) {
setCandidates(prepCandidates(data.candidates, data.expected_count));
setHelpRequested(data.help_requested);
setOthersRequestedHelp(data.others_requested_help);
- setDirty(false);
+ setDirty(DIRTY_STATUS.CLEAN);
})
.catch(error => {
console.log("error", error);
diff --git a/app/javascript/components/BingoBoards/CandidatesReviewTable.tsx b/app/javascript/components/BingoBoards/CandidatesReviewTable.tsx
index b0875aa1..c5d6f730 100644
--- a/app/javascript/components/BingoBoards/CandidatesReviewTable.tsx
+++ b/app/javascript/components/BingoBoards/CandidatesReviewTable.tsx
@@ -1,12 +1,12 @@
/* eslint-disable no-console */
-import React, { useState, useEffect, ReactNode } from "react";
+import React, { useState, useEffect, ReactNode, useRef } from "react";
import { useParams } from "react-router";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import RemoteAutosuggest from "./RemoteAutosuggest";
import { useTypedSelector } from "../infrastructure/AppReducers";
-import { startTask, endTask } from "../infrastructure/StatusSlice";
+import { startTask, endTask, useDirtyStatus, DIRTY_STATUS } from "../infrastructure/StatusSlice";
import axios from "axios";
import parse from "html-react-parser";
@@ -90,10 +90,14 @@ export default function CandidatesReviewTable(props: Props) {
const [acceptableUniqueConcepts, setAcceptableUniqueConcepts] = useState(0);
const dispatch = useDispatch();
- const [dirty, setDirty] = useState(false);
+ const [dirty, setDirty] = useDirtyStatus( );
+ const suppressDirtyRef = useRef(false);
useEffect(() => {
- setDirty(true);
+ if( suppressDirtyRef.current ) {
+ return;
+ }
+ setDirty(DIRTY_STATUS.DIRTY);
}, [reviewComplete, candidates]);
useEffect(() => {
@@ -160,6 +164,7 @@ export default function CandidatesReviewTable(props: Props) {
? `${endpoints.baseUrl}${bingoGameId}.json`
: `/${props.rootPath}${endpoints.baseUrl}${bingoGameId}.json`;
+ suppressDirtyRef.current = true;
axios
.get(url, {})
.then(response => {
@@ -184,17 +189,18 @@ export default function CandidatesReviewTable(props: Props) {
setCandidates(data.candidates);
setReviewStatus(t("review.data_loaded_msg"));
- setDirty(false);
+ setDirty(DIRTY_STATUS.CLEAN);
})
.catch(error => {
console.log("error", error);
})
.finally(() => {
dispatch(endTask());
+ suppressDirtyRef.current = false;
});
};
const saveFeedback = () => {
- setDirty(false);
+ suppressDirtyRef.current = true;
dispatch(startTask("saving"));
setReviewStatus(t("review.saving_msg"));
@@ -210,8 +216,8 @@ export default function CandidatesReviewTable(props: Props) {
})
.then(response => {
const data = response.data;
- setDirty(typeof data.success !== "undefined");
setReviewStatus(data.notice);
+ setDirty( typeof data.success !== "undefined" ? DIRTY_STATUS.CLEAN : DIRTY_STATUS.DIRTY );
})
.catch(error => {
const fail_data = new Object();
@@ -222,6 +228,7 @@ export default function CandidatesReviewTable(props: Props) {
})
.finally(() => {
dispatch(endTask("saving"));
+ suppressDirtyRef.current = false;
});
};
diff --git a/app/javascript/components/ConceptsTable.tsx b/app/javascript/components/ConceptsTable.tsx
index bbf09952..322bc5a0 100644
--- a/app/javascript/components/ConceptsTable.tsx
+++ b/app/javascript/components/ConceptsTable.tsx
@@ -15,7 +15,7 @@ import { Column } from "primereact/column";
import { Dialog } from "primereact/dialog";
import { useDispatch } from "react-redux";
-import { startTask, endTask, addMessage, Priorities } from "./infrastructure/StatusSlice";
+import { startTask, endTask, addMessage, Priorities, useDirtyStatus, DIRTY_STATUS } from "./infrastructure/StatusSlice";
import { InputText } from "primereact/inputtext";
enum OPT_COLS {
@@ -50,8 +50,15 @@ export default function ConceptsTable() {
const [sortBy, setSortBy] = useState("name");
const [sortDirection, setSortDirection] = useState(SortDirection.DESC);
+ const [dirty, setDirty] = useDirtyStatus();
const [editing, setEditing] = useState(false);
- const [dirty, setDirty] = useState(false);
+ const startEditing = () => {
+ setEditing(true);
+ }
+ const stopEditing = () => {
+ setEditing(false);
+ setDirty( DIRTY_STATUS.CLEAN );
+ }
const [conceptName, setConceptName] = useState("");
const [conceptId, setConceptId] = useState(-1);
@@ -63,7 +70,7 @@ export default function ConceptsTable() {
const setName = newName => {
setConceptName(newName);
- setDirty(true);
+ setDirty(DIRTY_STATUS.DIRTY);
};
const getConcepts = () => {
@@ -88,8 +95,8 @@ export default function ConceptsTable() {
const drillDown = event => {
setConceptId(event.data.id);
setConceptName(event.data.name);
- setEditing(true);
- setDirty(false);
+ startEditing();
+ setDirty(DIRTY_STATUS.CLEAN);
};
const updateConcept = (id, name) => {
dispatch(startTask("load"));
@@ -111,7 +118,7 @@ export default function ConceptsTable() {
setConcepts(tmpConcepts);
setConceptsRaw(tmpConcepts);
//statusActions.endTask("load");
- setEditing(false);
+ stopEditing();
dispatch( addMessage(t("update_success"), new Date(), Priorities.INFO ) );
})
.catch(error => {
@@ -195,12 +202,12 @@ export default function ConceptsTable() {