From ff6546e5ffc4c8863f6e766ae497bf10a7282e71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:45:53 -0500 Subject: [PATCH 1/8] Bump eslint-plugin-jasmine from 4.2.0 to 4.2.1 (#7173) Bumps [eslint-plugin-jasmine](https://github.com/tlvince/eslint-plugin-jasmine) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/tlvince/eslint-plugin-jasmine/releases) - [Commits](https://github.com/tlvince/eslint-plugin-jasmine/compare/v4.2.0...v4.2.1) --- updated-dependencies: - dependency-name: eslint-plugin-jasmine dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 198 ++------------------------------------------------- 2 files changed, 8 insertions(+), 192 deletions(-) diff --git a/package.json b/package.json index ce83dda131..4eefe55925 100644 --- a/package.json +++ b/package.json @@ -352,7 +352,7 @@ "eslint-config-airbnb": "19.0.4", "eslint-plugin-cypress": "^3.5.0", "eslint-plugin-import": "v2.29.1", - "eslint-plugin-jasmine": "4.2.0", + "eslint-plugin-jasmine": "4.2.1", "eslint-plugin-jest": "v28.8.0", "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-node": "^11.1.0", diff --git a/yarn.lock b/yarn.lock index 5309f40e62..5fdb725712 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1925,13 +1925,6 @@ ms "^2.1.3" secure-json-parse "^2.4.0" -"@emnapi/runtime@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.2.0.tgz#71d018546c3a91f3b51106530edbc056b9f2f2e3" - integrity sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ== - dependencies: - tslib "^2.4.0" - "@emotion/use-insertion-effect-with-fallbacks@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" @@ -2238,119 +2231,6 @@ version "0.2.4" resolved "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz" -"@img/sharp-darwin-arm64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" - integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.0.4" - -"@img/sharp-darwin-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" - integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.0.4" - -"@img/sharp-libvips-darwin-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" - integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== - -"@img/sharp-libvips-darwin-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" - integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== - -"@img/sharp-libvips-linux-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" - integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== - -"@img/sharp-libvips-linux-arm@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" - integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== - -"@img/sharp-libvips-linux-s390x@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" - integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== - -"@img/sharp-libvips-linux-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" - integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== - -"@img/sharp-libvips-linuxmusl-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" - integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== - -"@img/sharp-libvips-linuxmusl-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" - integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== - -"@img/sharp-linux-arm64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" - integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.0.4" - -"@img/sharp-linux-arm@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" - integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.0.5" - -"@img/sharp-linux-s390x@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" - integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== - optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.0.4" - -"@img/sharp-linux-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" - integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.0.4" - -"@img/sharp-linuxmusl-arm64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" - integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" - -"@img/sharp-linuxmusl-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" - integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.0.4" - -"@img/sharp-wasm32@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" - integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== - dependencies: - "@emnapi/runtime" "^1.2.0" - -"@img/sharp-win32-ia32@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" - integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== - -"@img/sharp-win32-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" - integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== - "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -5467,13 +5347,6 @@ "@types/mime" "*" "@types/node" "*" -"@types/sharp@^0.29.2": - version "0.29.2" - resolved "https://registry.npmjs.org/@types/sharp/-/sharp-0.29.2.tgz" - integrity sha512-tIbMvtPa8kMyFMKNhpsPT1HO3CgXLuiCAA8bxHAGAZLyALpYvYc4hUu3pu0+3oExQA5LwvHrWp+OilgXCYVQgg== - dependencies: - "@types/node" "*" - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -7346,32 +7219,16 @@ color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa" - integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - color-support@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" @@ -8400,7 +8257,7 @@ detect-indent@^6.1.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -detect-libc@^2.0.0, detect-libc@^2.0.3: +detect-libc@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== @@ -9300,10 +9157,10 @@ eslint-plugin-import@v2.29.1: semver "^6.3.1" tsconfig-paths "^3.15.0" -eslint-plugin-jasmine@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.0.tgz#ed0fe988b6e3b123905a7bf68d77239649fd018c" - integrity sha512-zSCsnP4gMqBSt8jApExP0ja43nAI1fpAD5kY+knrIJylBxC/LEth25PkqcKJqW32GjesjsiA1SSSR3Z5qIranA== +eslint-plugin-jasmine@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.1.tgz#80925dc6b24ee263eb834bedb09ff4abf3740b3d" + integrity sha512-Vwecc66rjMgz2e9UtGScsUdo6D+SbfgPA4Kf0zdAl4+5IQMRL0mXd8973MaZuYYF89XpRjQEGl5TNmg2Bv+KcQ== eslint-plugin-jest@v28.8.0: version "28.8.0" @@ -11257,11 +11114,6 @@ is-arrayish@^0.2.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-async-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" @@ -16087,7 +15939,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: +semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -16201,35 +16053,6 @@ shallow-compare@^1.2.1: resolved "https://registry.npmjs.org/shallow-compare/-/shallow-compare-1.2.2.tgz" integrity sha512-LUMFi+RppPlrHzbqmFnINTrazo0lPNwhcgzuAXVVcfy/mqPDrQmHAyz5bvV0gDAuRFrk804V0HpQ6u9sZ0tBeg== -sharp@^0.33.5: - version "0.33.5" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" - integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== - dependencies: - color "^4.2.3" - detect-libc "^2.0.3" - semver "^7.6.3" - optionalDependencies: - "@img/sharp-darwin-arm64" "0.33.5" - "@img/sharp-darwin-x64" "0.33.5" - "@img/sharp-libvips-darwin-arm64" "1.0.4" - "@img/sharp-libvips-darwin-x64" "1.0.4" - "@img/sharp-libvips-linux-arm" "1.0.5" - "@img/sharp-libvips-linux-arm64" "1.0.4" - "@img/sharp-libvips-linux-s390x" "1.0.4" - "@img/sharp-libvips-linux-x64" "1.0.4" - "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" - "@img/sharp-libvips-linuxmusl-x64" "1.0.4" - "@img/sharp-linux-arm" "0.33.5" - "@img/sharp-linux-arm64" "0.33.5" - "@img/sharp-linux-s390x" "0.33.5" - "@img/sharp-linux-x64" "0.33.5" - "@img/sharp-linuxmusl-arm64" "0.33.5" - "@img/sharp-linuxmusl-x64" "0.33.5" - "@img/sharp-wasm32" "0.33.5" - "@img/sharp-win32-ia32" "0.33.5" - "@img/sharp-win32-x64" "0.33.5" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -16291,13 +16114,6 @@ simple-html-tokenizer@^0.1.1: resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz#05c2eec579ffffe145a030ac26cfea61b980fabe" integrity sha512-Mc/gH3RvlKvB/gkp9XwgDKEWrSYyefIJPGG8Jk1suZms/rISdUuVEMx5O1WBnTWaScvxXDvGJrZQWblUmQHjkQ== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" From eceec43888cf612a42ee577a851bc2408723578f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:07:52 -0500 Subject: [PATCH 2/8] Bump prettier from 3.2.5 to 3.3.3 (#7174) Bumps [prettier](https://github.com/prettier/prettier) from 3.2.5 to 3.3.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.2.5...3.3.3) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mercy --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4eefe55925..7cfa17ef52 100644 --- a/package.json +++ b/package.json @@ -376,7 +376,7 @@ "nodemon": "^3.1.4", "plop": "^4.0.1", "postcss": "^8.4.41", - "prettier": "3.2.5", + "prettier": "3.3.3", "puppeteer": "^13.5.2", "react-dnd-test-backend": "15.1.1", "redux-mock-store": "^1.5.4", diff --git a/yarn.lock b/yarn.lock index 5fdb725712..c3b620b9b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14596,10 +14596,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@3.2.5, prettier@^3.1.1: - version "3.2.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" - integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== +prettier@3.3.3, prettier@^3.1.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== prettier@^1.16.4: version "1.19.1" From 14238185b71d312fefc3ed88793e1f894f5c6665 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:06:07 -0500 Subject: [PATCH 3/8] Bump webpack-dev-middleware from 7.3.0 to 7.4.2 (#7169) Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 7.3.0 to 7.4.2. - [Release notes](https://github.com/webpack/webpack-dev-middleware/releases) - [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v7.3.0...v7.4.2) --- updated-dependencies: - dependency-name: webpack-dev-middleware dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Santiago <71732018+Zasa-san@users.noreply.github.com> Co-authored-by: Mercy --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7cfa17ef52..0a28a88435 100644 --- a/package.json +++ b/package.json @@ -395,7 +395,7 @@ "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "5.1.4", - "webpack-dev-middleware": "7.3.0", + "webpack-dev-middleware": "7.4.2", "webpack-hot-middleware": "^2.26.1", "worker-loader": "^3.0.8" }, diff --git a/yarn.lock b/yarn.lock index c3b620b9b3..d0f6fa296e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17764,10 +17764,10 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.3.0.tgz#5975ea41271083dc5678886b99d4c058382fb311" - integrity sha512-xD2qnNew+F6KwOGZR7kWdbIou/ud7cVqLEXeK1q0nHcNsX/u7ul/fSdlOTX4ntSL5FNFy7ZJJXbf0piF591JYw== +webpack-dev-middleware@7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" + integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA== dependencies: colorette "^2.0.10" memfs "^4.6.0" From 35bebab8627cd6e080ff43de5027d2e08f404ce7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:56:48 -0500 Subject: [PATCH 4/8] Bump stopword from 3.0.1 to 3.1.1 (#7168) Bumps [stopword](https://github.com/fergiemcdowall/stopword) from 3.0.1 to 3.1.1. - [Release notes](https://github.com/fergiemcdowall/stopword/releases) - [Commits](https://github.com/fergiemcdowall/stopword/compare/v3.0.1...v3.1.1) --- updated-dependencies: - dependency-name: stopword dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mercy --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0a28a88435..a977436b43 100644 --- a/package.json +++ b/package.json @@ -238,7 +238,7 @@ "socket.io": "4.7.5", "socket.io-client": "4.7.5", "socket.io-parser": "4.2.4", - "stopword": "^3.0.1", + "stopword": "^3.1.1", "superagent": "10.1.0", "svg-captcha": "^1.4.0", "tiny-cookie": "^2.5.1", diff --git a/yarn.lock b/yarn.lock index d0f6fa296e..4ca29c27a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16370,10 +16370,10 @@ stdin-discarder@^0.2.1: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stopword@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/stopword/-/stopword-3.0.1.tgz#258e812334d3287c1647bce2ec73426d46a6747b" - integrity sha512-pz2XNVtoApA7eSX1viol5ybGGG2Re2bI7/+8TfeaMb09C9NEWgabr7HHf4DbqgrmEuI62sH2BcAEHVAlreCmrQ== +stopword@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/stopword/-/stopword-3.1.1.tgz#ce3cf748cafd962904902dd050f6f16ca42d3ec0" + integrity sha512-TzJdIuzqJNo6IaFvrF3fYqu08uJ/0VMsdABl6d6+dt6daD7QeHJnMt9sPqhVIxEmNaaeE8+eandVPJv9RhAL5Q== store2@^2.14.2: version "2.14.2" From 7c8516fa2f2847cc5db99900d20bf12696fa3fae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:38:17 -0300 Subject: [PATCH 5/8] Bump cypress from 13.13.2 to 13.14.1 (#7177) Bumps [cypress](https://github.com/cypress-io/cypress) from 13.13.2 to 13.14.1. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.13.2...v13.14.1) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a977436b43..789935e160 100644 --- a/package.json +++ b/package.json @@ -342,7 +342,7 @@ "copy-webpack-plugin": "12.0.2", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "cypress": "13.13.2", + "cypress": "13.14.1", "cypress-axe": "^1.5.0", "cypress-plugin-snapshots": "^1.4.4", "cypress-real-events": "^1.13.0", diff --git a/yarn.lock b/yarn.lock index 4ca29c27a9..dffb41dc4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7854,10 +7854,10 @@ cypress-real-events@^1.13.0: resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.13.0.tgz#6b7cd32dcac172db1493608f97a2576c7d0bd5af" integrity sha512-LoejtK+dyZ1jaT8wGT5oASTPfsNV8/ClRp99ruN60oPj8cBJYod80iJDyNwfPAu4GCxTXOhhAv9FO65Hpwt6Hg== -cypress@13.13.2: - version "13.13.2" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.13.2.tgz#c71f8d92056c430b1b879e5313f6de25ccce0eda" - integrity sha512-PvJQU33933NvS1StfzEb8/mu2kMy4dABwCF+yd5Bi7Qly1HOVf+Bufrygee/tlmty/6j5lX+KIi8j9Q3JUMbhA== +cypress@13.14.1: + version "13.14.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.14.1.tgz#05875bbbf6333e858a92aed33ba67d7ddf8370d7" + integrity sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg== dependencies: "@cypress/request" "^3.0.1" "@cypress/xvfb" "^1.2.4" From c80e682ef93e17f333780af7e88c59469d9ffb62 Mon Sep 17 00:00:00 2001 From: Santiago <71732018+Zasa-san@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:48:31 -0300 Subject: [PATCH 6/8] Tables migration (#7131) * migrate user table * migrate translations table * migrate pagelist table * migrate language list * migrate custom uploads * migrated activity log table * update menu table + refactor + test * migrate relationships list table * add default sorting state to log * lint fix * updated menu blocker * updated pages e2e * refactor + more tests * permissions table migrated * filters table migration + empty groups fix * typo fix * fix filters behavior * remove translate for consistency * remove unneeded translate * sort buttons * refactor .map * apply sr-only on the content, not the cell, for actions * correctly handle changes and reset * add rowId afther rerender * typo fixes * removed previous table and unused dnd component * update translations --- .../Layouts/DragAndDrop/Container.tsx | 72 -- .../Layouts/DragAndDrop/DnDDefinitions.ts | 250 ------ .../Layouts/DragAndDrop/DragSource.tsx | 34 - .../Layouts/DragAndDrop/DraggableItem.tsx | 151 ---- .../Layouts/DragAndDrop/DropZone.tsx | 68 -- .../Layouts/DragAndDrop/SortFunction.ts | 95 -- .../Components/Layouts/DragAndDrop/index.ts | 7 - .../DragAndDrop/specs/DragAndDrop.cy.tsx | 277 ------ .../Layouts/DragAndDrop/useDnDContext.tsx | 104 --- .../UI/{TableV2 => Table}/DnDComponents.tsx | 0 .../V2/Components/UI/Table/DraggableRow.tsx | 87 -- .../UI/{TableV2 => Table}/GroupComponents.tsx | 0 .../RowSelectComponents.tsx | 0 .../UI/{TableV2 => Table}/SortingChevrons.tsx | 0 app/react/V2/Components/UI/Table/Table.tsx | 335 ++++--- .../V2/Components/UI/Table/TableBody.tsx | 145 ---- .../V2/Components/UI/Table/TableElements.tsx | 115 --- .../V2/Components/UI/Table/TableHeader.tsx | 37 - app/react/V2/Components/UI/Table/TableRow.tsx | 81 -- .../UI/{TableV2 => Table}/helpers.ts | 0 app/react/V2/Components/UI/Table/index.ts | 2 + .../UI/{TableV2 => Table}/specs/fixtures.ts | 0 .../{TableV2 => Table}/specs/helpers.spec.ts | 0 app/react/V2/Components/UI/TableV2/Table.tsx | 250 ------ app/react/V2/Components/UI/index.ts | 10 +- app/react/V2/Components/UI/specs/Table.cy.tsx | 820 ++++++++++++------ .../V2/Components/UI/specs/TableV2.cy.tsx | 684 --------------- app/react/V2/Components/componentWrappers.tsx | 45 - .../Settings/ActivityLog/ActivityLog.tsx | 24 +- .../Settings/ActivityLog/ActivityLogLoader.ts | 9 +- .../ActivityLog/components/TableElements.tsx | 23 +- .../Settings/CustomUploads/CustomUploads.tsx | 46 +- .../components/EditFileSidepanel.tsx | 8 +- .../CustomUploads/components/UploadsTable.tsx | 24 +- .../Routes/Settings/Filters/FiltersTable.tsx | 133 +-- .../Filters/components/FiltersSidepanel.tsx | 25 +- .../Filters/components/TableComponents.tsx | 71 +- .../Settings/Filters/components/helpers.ts | 107 +-- .../Settings/Filters/components/index.ts | 3 +- .../Filters/components/sidepanelAtom.ts | 4 +- .../Filters/components/specs/fixtures.ts | 72 ++ .../Filters/components/specs/helpers.spec.ts | 178 ++-- .../Settings/Languages/LanguagesList.tsx | 34 +- .../Languages/components/TableComponents.tsx | 8 +- .../Routes/Settings/MenuConfig/MenuConfig.tsx | 179 ++-- .../MenuConfig/components/MenuForm.tsx | 222 +++-- .../MenuConfig/components/TableComponents.tsx | 40 +- .../MenuConfig/components/specs/fixtures.ts | 49 ++ .../components/specs/updateLinks.spec.ts | 129 +++ .../V2/Routes/Settings/MenuConfig/shared.ts | 30 + .../V2/Routes/Settings/Pages/PagesList.tsx | 39 +- .../Pages/components/PageListTable.tsx | 21 +- .../RelationshipTypes/RelationshipTypes.tsx | 53 +- .../RelationshipTypes/components/Form.tsx | 12 +- .../components/TableComponents.tsx | 29 +- .../Translations/TranslationsList.tsx | 47 +- .../components/TableComponents.tsx | 8 +- app/react/V2/Routes/Settings/Users/Users.tsx | 74 +- .../Users/components/GroupFormSidepanel.tsx | 18 +- .../Settings/Users/components/ListOfItems.tsx | 17 +- .../Users/components/PermissionsListModal.tsx | 20 +- .../Users/components/TableComponents.tsx | 133 ++- .../Users/components/UserFormSidepanel.tsx | 21 +- app/react/V2/Routes/Settings/Users/types.ts | 7 +- app/react/V2/api/pages/index.ts | 2 +- app/react/V2/api/users/index.ts | 15 +- app/react/stories/DragAndDrop.stories.tsx | 86 -- app/react/stories/Table.stories.tsx | 322 +++++-- app/react/stories/TableV2.stories.tsx | 275 ------ .../dragAndDrop/DragAndDropComponents.tsx | 187 ---- app/react/stories/table/TableComponents.tsx | 198 ----- contents/ui-translations/ar.csv | 1 - contents/ui-translations/en.csv | 1 - contents/ui-translations/es.csv | 1 - contents/ui-translations/fr.csv | 1 - contents/ui-translations/ko.csv | 1 - contents/ui-translations/my.csv | 1 - contents/ui-translations/ru.csv | 1 - contents/ui-translations/th.csv | 1 - contents/ui-translations/tr.csv | 1 - ... render a list with all pages names #0.png | Bin 60222 -> 48315 bytes cypress/e2e/pages/pages.cy.ts | 3 +- ...slations list should be accessible #0.png | Bin 75223 -> 53883 bytes cypress/e2e/settings/filters.cy.ts | 204 ++--- cypress/e2e/settings/menu.cy.ts | 51 +- package.json | 2 - yarn.lock | 9 +- 87 files changed, 2355 insertions(+), 4594 deletions(-) delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/Container.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/index.ts delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx rename app/react/V2/Components/UI/{TableV2 => Table}/DnDComponents.tsx (100%) delete mode 100644 app/react/V2/Components/UI/Table/DraggableRow.tsx rename app/react/V2/Components/UI/{TableV2 => Table}/GroupComponents.tsx (100%) rename app/react/V2/Components/UI/{TableV2 => Table}/RowSelectComponents.tsx (100%) rename app/react/V2/Components/UI/{TableV2 => Table}/SortingChevrons.tsx (100%) delete mode 100644 app/react/V2/Components/UI/Table/TableBody.tsx delete mode 100644 app/react/V2/Components/UI/Table/TableElements.tsx delete mode 100644 app/react/V2/Components/UI/Table/TableHeader.tsx delete mode 100644 app/react/V2/Components/UI/Table/TableRow.tsx rename app/react/V2/Components/UI/{TableV2 => Table}/helpers.ts (100%) create mode 100644 app/react/V2/Components/UI/Table/index.ts rename app/react/V2/Components/UI/{TableV2 => Table}/specs/fixtures.ts (100%) rename app/react/V2/Components/UI/{TableV2 => Table}/specs/helpers.spec.ts (100%) delete mode 100644 app/react/V2/Components/UI/TableV2/Table.tsx delete mode 100644 app/react/V2/Components/UI/specs/TableV2.cy.tsx delete mode 100644 app/react/V2/Components/componentWrappers.tsx create mode 100644 app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts create mode 100644 app/react/V2/Routes/Settings/MenuConfig/components/specs/fixtures.ts create mode 100644 app/react/V2/Routes/Settings/MenuConfig/components/specs/updateLinks.spec.ts create mode 100644 app/react/V2/Routes/Settings/MenuConfig/shared.ts delete mode 100644 app/react/stories/DragAndDrop.stories.tsx delete mode 100644 app/react/stories/TableV2.stories.tsx delete mode 100644 app/react/stories/dragAndDrop/DragAndDropComponents.tsx delete mode 100644 app/react/stories/table/TableComponents.tsx diff --git a/app/react/V2/Components/Layouts/DragAndDrop/Container.tsx b/app/react/V2/Components/Layouts/DragAndDrop/Container.tsx deleted file mode 100644 index c06d14c00e..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/Container.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { type FC } from 'react'; -import { IDraggable } from 'app/V2/shared/types'; -import { DraggableItem } from './DraggableItem'; -import { DropZone } from './DropZone'; -import type { IDnDContext } from './DnDDefinitions'; - -interface IItemComponentProps { - item: IDraggable; - context: IDnDContext; - index: number; -} - -interface ContainerProps { - context: IDnDContext; - itemComponent?: FC>; - iconHandle?: boolean; - className?: string; - parent?: IDraggable; -} - -/* eslint-disable comma-spacing */ -const Container = ({ - context, - iconHandle = false, - itemComponent, - className, - parent, -}: ContainerProps) => { - const currentItems = parent ? parent.value.items || [] : context.activeItems; - return ( -
-
-
    - {currentItems - .filter((item: IDraggable) => item) - .map((item: IDraggable, index: number) => ( - - <> - {itemComponent && itemComponent({ item, context, index })} - {!itemComponent && context.getDisplayName(item)} - - - ))} -
- -
-
- ); -}; - -Container.defaultProps = { - iconHandle: false, - itemComponent: undefined, - className: '', - parent: undefined, -}; - -export type { IItemComponentProps }; -export { Container }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts b/app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts deleted file mode 100644 index 139e1b3deb..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { Dispatch } from 'react'; -import { get, has, omit } from 'lodash'; -import update from 'immutability-helper'; -import { type IDraggable, ItemTypes } from 'app/V2/shared/types'; -import ID from 'shared/uniqueID'; - -interface IDnDOperations { - getDisplayName: (item: IDraggable) => string; - sortCallback?: Function; - onChange?: (items: T[]) => void; - itemsProperty?: string; - allowEditGroupsWithDnD?: boolean; -} - -interface IDnDContext { - itemsProperty: string; - type: ItemTypes; - addItem: (item: IDraggable) => void; - removeItem: (item: IDraggable) => void; - updateItem: (values: IDraggable) => void; - updateActiveItems: (items: T[]) => IDraggable[]; - sort: Function; - activeItems: IDraggable[]; - availableItems: IDraggable[]; - getDisplayName: (item: IDraggable) => string; - operations: IDnDOperations; - setDragging: React.Dispatch>; -} - -interface IDnDContextState { - activeItems: IDraggable[]; - setActiveItems: React.Dispatch[]>>; - availableItems: IDraggable[]; - setAvailableItems: React.Dispatch[]>>; - itemsProperty: string; - operations: IDnDOperations; -} - -const setIdAndParent = (item: IDraggable, parent?: IDraggable) => { - const idField = has(item.value, 'dndId') ? 'dndId' : 'id'; - const dndId = has(item.value, idField) ? get(item.value, idField) : ID(); - return { ...item, dndId, ...(parent ? { parent } : {}) }; -}; - -const mapWithParent = ( - items: T[], - parent?: IDraggable, - itemsProperty: string = 'items', - allowEditGroupsWithDnD: boolean = true -): IDraggable[] => - items.map(item => { - const draggableItem = { - value: item, - ...(parent === undefined - ? { container: 'root', fixed: !allowEditGroupsWithDnD } - : { fixed: !allowEditGroupsWithDnD }), - } as IDraggable; - const subItems = get(draggableItem.value, itemsProperty); - const itemWithId: IDraggable = setIdAndParent(draggableItem, parent); - if (subItems && subItems.length > 0) { - return { - ...itemWithId, - value: { - ...itemWithId.value, - items: mapWithParent( - subItems as T[], - itemWithId, - itemsProperty, - allowEditGroupsWithDnD - ), - }, - }; - } - return itemWithId; - }) as IDraggable[]; - -const mapWithID = (items: IDraggable[]) => - items.map(item => setIdAndParent(item)); - -const removeChildFromParent = (state: IDnDContextState, newItem: IDraggable) => { - if (newItem.parent) { - const indexOfCurrentParent = state.activeItems.findIndex( - ai => ai.dndId === newItem.parent!.dndId - ); - state.setActiveItems((prevActiveItems: IDraggable[]) => { - const index = prevActiveItems[indexOfCurrentParent].value.items.findIndex( - (ai: IDraggable) => ai.dndId === newItem.dndId - ); - return update(prevActiveItems, { - [indexOfCurrentParent]: { value: { items: { $splice: [[index, 1]] } } }, - }); - }); - } -}; - -const findItemIndex = (items: IDraggable[], searchedItem: IDraggable) => - items.findIndex(item => item.dndId === searchedItem.dndId); - -const removeItemFromList = ( - setActiveItems: Dispatch[]>>, - indexOfNewItem: number -) => { - if (indexOfNewItem > -1) { - setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - $splice: [[indexOfNewItem, 1]], - }) - ); - } -}; - -const addItemToParent = ( - state: IDnDContextState, - indexOfNewItem: number, - newItem: IDraggable, - parent: IDraggable -) => { - removeItemFromList(state.setActiveItems, indexOfNewItem); - state.setActiveItems((prevActiveItems: IDraggable[]) => { - const indexOfParent = findItemIndex(prevActiveItems, parent); - if (indexOfParent > -1) { - return prevActiveItems[indexOfParent].value.items - ? update(prevActiveItems, { - [indexOfParent]: { - value: { - items: { - $push: [ - { - ...omit(newItem, ['parent', 'container', 'value.items']), - parent, - }, - ], - }, - }, - }, - }) - : update(prevActiveItems, { - [indexOfParent]: { - value: { - items: { - $set: [{ ...newItem, parent }], - }, - }, - }, - }); - } - return prevActiveItems; - }); -}; - -const addActiveItem = - (state: IDnDContextState) => - (newItem: IDraggable, parent?: IDraggable) => { - removeChildFromParent(state, newItem); - const indexOfNewItem = findItemIndex(state.activeItems, newItem); - if (parent) { - addItemToParent(state, indexOfNewItem, newItem, parent); - } else if (indexOfNewItem === -1) { - state.setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - //@ts-ignore - $push: [omit(newItem, ['parent', 'container', 'value.items'])], - }) - ); - } - const indexOfSource = findItemIndex(state.availableItems, newItem); - removeItemFromList(state.setAvailableItems, indexOfSource); - }; - -const removeActiveItem = - (state: IDnDContextState) => - (item: IDraggable) => { - if (item.parent !== undefined) { - removeChildFromParent(state, item); - } else { - const index = findItemIndex(state.activeItems, item); - removeItemFromList(state.setActiveItems, index); - } - const availableSubItems: IDraggable[] = (item.value.items || []).map((ai: IDraggable) => - omit(ai, ['parent', 'container']) - ); - const releasedItem = omit(item, ['parent', 'container', 'value.items']); - state.setAvailableItems((prevAvailableItems: IDraggable[]) => - update(prevAvailableItems, { - //@ts-ignore - $push: [releasedItem, ...availableSubItems], - }) - ); - }; - -const sortChildren = ( - state: IDnDContextState, - { - currentItem, - target, - dragIndex, - hoverIndex, - }: { currentItem: IDraggable; target: IDraggable; dragIndex: number; hoverIndex: number } -) => { - const indexOfParent = findItemIndex(state.activeItems, currentItem.parent!); - const targetIndex = findItemIndex(state.activeItems[indexOfParent].value.items || [], target); - if (targetIndex === hoverIndex) { - state.setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - [indexOfParent]: { - value: { - items: { - $splice: [ - [dragIndex, 1], - [hoverIndex, 0, prevActiveItems[indexOfParent].value.items[dragIndex]], - ], - }, - }, - }, - }) - ); - } -}; - -const sortParents = ( - state: IDnDContextState, - { - dragIndex, - hoverIndex, - }: { currentItem: IDraggable; target: IDraggable; dragIndex: number; hoverIndex: number } -) => { - state.setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - $splice: [ - [dragIndex, 1], - [hoverIndex, 0, prevActiveItems[dragIndex]], - ], - }) - ); -}; -const sortActiveItems = - (state: IDnDContextState) => - (currentItem: IDraggable, target: IDraggable, dragIndex: number, hoverIndex: number) => { - if (currentItem.parent !== undefined) { - sortChildren(state, { currentItem, target, dragIndex, hoverIndex }); - } else { - sortParents(state, { currentItem, target, dragIndex, hoverIndex }); - } - if (state.operations.sortCallback) { - state.operations.sortCallback(state.activeItems.map(item => item.value)); - } - }; - -export type { IDnDContext, IDnDContextState, IDnDOperations }; -export { addActiveItem, removeActiveItem, sortActiveItems, mapWithParent, mapWithID }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx b/app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx deleted file mode 100644 index f18ff3526f..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { IDraggable } from 'app/V2/shared/types'; -import { DraggableItem } from './DraggableItem'; -import type { IDnDContext } from './DnDDefinitions'; -import { withDnD } from '../../componentWrappers'; - -interface DragSourceComponentProps { - context: IDnDContext; - className?: string; -} -/* eslint-disable comma-spacing */ -const DragSourceComponent = ({ context, className = '' }: DragSourceComponentProps) => ( -
-
    - {context.availableItems.map((item: IDraggable, index: number) => ( - - {context.getDisplayName(item)} - - ))} -
-
-); - -/* eslint-disable comma-spacing */ -const DragSource = (props: DragSourceComponentProps) => - withDnD(DragSourceComponent)(props); - -export { DragSource }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx b/app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx deleted file mode 100644 index a221aad7e1..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { RefObject, useEffect, useRef } from 'react'; -import type { DragSourceMonitor } from 'react-dnd/dist/types/monitors'; -import { Bars3Icon } from '@heroicons/react/20/solid'; - -import type { IDraggable } from 'app/V2/shared/types'; -import { hoverSortable } from './SortFunction'; -import { IItemComponentProps } from './Container'; -import type { IDnDContext } from './DnDDefinitions'; -import { withDnD } from '../../componentWrappers'; - -interface DraggableItemProps extends React.PropsWithChildren { - item: IDraggable; - useDrag?: Function; - useDrop?: Function; - iconHandle?: boolean; - index: number; - context: any; - className?: string; - omitIcon?: boolean; - wrapperType?: 'li' | 'tr' | 'div'; - container?: string; - previewRef?: RefObject; -} - -type DraggedResult = { - item: IDraggable; - index: number; - container: string; -}; - -/* eslint-disable comma-spacing */ -const hasValidContext = (dropContext?: IDnDContext) => dropContext !== undefined; - -/* eslint-disable comma-spacing */ -const isNotAutoContained = ( - currentItem: IDraggable, - draggedResult: DraggedResult, - dropParent?: { dndId?: string; item?: IDraggable } -) => - (draggedResult.item.container !== currentItem.container || - dropParent === undefined || - dropParent?.dndId !== draggedResult.item.parent?.dndId || - draggedResult.item.container === undefined) && - (dropParent === undefined || draggedResult.item.dndId !== dropParent.dndId); - -/* eslint-disable comma-spacing */ -const hasNoItems = (currentItem: IDraggable) => - currentItem.value.items === undefined || currentItem.value.items.length === 0; - -const getOpacityLevel = (isDragging: boolean) => (isDragging ? 0 : 1); - -const getIconHandleClass = (condition: boolean) => (condition ? 'cursor-move' : ''); - -/* eslint-disable comma-spacing */ -const elementTestId = ( - item: IDraggable, - context: any, - container: string | undefined, - index: number -) => - `${ - (item.parent ? `group_${context.getDisplayName(item.parent)}` : '') || container || 'available' - }-draggable-item-${index}`; - -/* eslint-disable comma-spacing */ -// eslint-disable-next-line max-statements -const DraggableItemComponent = ({ - item, - useDrag = () => {}, - useDrop = () => {}, - iconHandle = false, - index, - children, - context, - className, - omitIcon = false, - wrapperType = 'li', - container, - previewRef, -}: DraggableItemProps) => { - const ref = useRef(null); - const [, drop] = useDrop({ - accept: context.type, - item: { item: { ...item, container }, container, index }, - hover: hoverSortable(ref, { ...item, container }, index, context.setDragging, context.sort), - }); - const [{ isDragging, handlerId }, drag, preview] = useDrag({ - type: context.type, - item: { item: { ...item, container }, index }, - end: (draggedResult: DraggedResult, monitor: DragSourceMonitor) => { - context.setDragging(false); - const { context: dropContext, parent: dropParent } = - monitor.getDropResult & { parent: IDraggable }>() || {}; - - const draggedItem = item.dndId === draggedResult.item.dndId ? draggedResult.item : item; - if ( - hasValidContext(dropContext) && - isNotAutoContained(draggedItem, draggedResult, dropParent) && - hasNoItems(item) && - !draggedResult.item.fixed - ) { - context.addItem(draggedResult.item, dropParent); - } - }, - collect: (monitor: any) => ({ - isDragging: monitor.isDragging(), - handlerId: monitor.getHandlerId(), - }), - }); - - const previewReference = previewRef || ref; - - useEffect(() => { - preview(previewReference); - }, [preview, previewReference]); - - const opacity = getOpacityLevel(isDragging); - - if (previewReference && previewReference.current) { - // eslint-disable-next-line no-param-reassign - previewReference.current.style.opacity = getOpacityLevel(isDragging).toString(); - } - - drag(drop(ref)); - drag(drop(previewReference)); - - const TagName = wrapperType; - - return ( - (item, context, container, index)} - style={{ opacity }} - data-handler-id={handlerId} - key={TagName + item.dndId} - > - {!omitIcon && wrapperType === 'li' && ( - - )} - {children} - - ); -}; - -const DraggableItem = withDnD(DraggableItemComponent); - -export { DraggableItem, hoverSortable }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx b/app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx deleted file mode 100644 index 6150deb6e1..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { PropsWithChildren, RefObject, useRef } from 'react'; -import type { DropTargetMonitor } from 'react-dnd/dist/types/monitors'; -import { Translate } from 'app/I18N'; -import { IDraggable, ItemTypes } from 'app/V2/shared/types'; -import { withDnD } from '../../componentWrappers'; - -interface DroppableProps extends PropsWithChildren { - name: string; - type: ItemTypes; - context: any; - useDrop?: Function; - parent?: IDraggable; - wrapperType?: 'tbody' | 'div' | 'tr'; - className?: string; - activeClassName?: string; - innerRef?: RefObject; -} - -/* eslint-disable comma-spacing */ -const DropZoneComponent = ({ - name, - useDrop = () => {}, - type, - context, - parent, - children, - wrapperType = 'div', - className, - activeClassName, - innerRef, -}: DroppableProps) => { - const ref = useRef(null); - const [{ canDrop, isOver }, drop] = useDrop(() => ({ - accept: type, - drop: () => ({ name, context, parent }), - collect: (monitor: DropTargetMonitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - })); - - const isActive = canDrop && isOver; - - const classesByTag: { [k: string]: string } = { - div: - className || - 'flex flex-col items-center justify-center text-gray-400 uppercase p-15 h-14 text-base m-5 border border-gray-300 border-dashed rounded-sm cursor-pointer bg-gray-50', - tr: className || '', - 'active-div': - activeClassName || - ' bg-gray-800 dark:bg-gray-700 bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600', - 'active-tr': activeClassName || '', - }; - - const baseClassName = classesByTag[wrapperType] || ''; - const activeClasses = classesByTag[`active-${wrapperType}`] || ''; - const dropClassName = isActive ? baseClassName + activeClasses : className || baseClassName; - - const TagName = wrapperType; - - return ( - - {children || Drag items here} - - ); -}; -const DropZone = withDnD(DropZoneComponent); -export { DropZone }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts b/app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts deleted file mode 100644 index 482cc9cd25..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { RefObject } from 'react'; -import type { XYCoord } from 'react-dnd/dist/types/monitors'; -import { IDraggable } from 'app/V2/shared/types'; - -const checkSortArea = ( - monitor: any, - hoverBoundingRect: DOMRect, - dragIndex: number, - hoverIndex: number -) => { - // Get vertical middle - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - - // Determine mouse position - const clientOffset = monitor.getClientOffset(); - - // Get pixels to the top - const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; - - // Only perform the move when the mouse has crossed half of the items height - // When dragging downwards, only move when the cursor is below 50% - // When dragging upwards, only move when the cursor is above 50% - - // Dragging downwards OR Dragging upwards - if ( - (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) || - (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) - ) { - return true; - } - return false; -}; - -const isOutOfSortArea = ( - ref: RefObject, - monitor: any, - dragIndex: number, - hoverIndex: number -) => - // Determine rectangle on screen - // No hoverBoundingRect AND Don't replace items with themselves - !ref.current || - !ref.current.getBoundingClientRect() || - dragIndex === hoverIndex || - checkSortArea(monitor, ref.current.getBoundingClientRect(), dragIndex, hoverIndex); - -const isNotSortableItem = ( - currentItem: { dndId: string; item: IDraggable }, - target: IDraggable & { ID?: string } -) => - (currentItem.item.parent && !target.parent) || - currentItem.dndId === target.dndId || - currentItem.item.container === undefined; - -const hoverSortable = - ( - ref: RefObject, - target: IDraggable, - index: number, - setDragging: React.Dispatch>, - sortFunction?: Function - ) => - ( - currentItem: { - index: number; - dndId: string; - type: string; - item: IDraggable; - }, - monitor: any - ) => { - setDragging(true); - const dragIndex = currentItem.index; - const hoverIndex = index; - const draggingDown = monitor.getDifferenceFromInitialOffset().y > 0; - const invalidSort = - isNotSortableItem(currentItem, target) || - (draggingDown && dragIndex > hoverIndex && hoverIndex === 0) || - isOutOfSortArea(ref, monitor, dragIndex, hoverIndex); - - if (!ref.current || !sortFunction || !monitor.isOver({ shallow: false }) || invalidSort) { - return; - } - // Time to actually perform the action - sortFunction(currentItem.item, target, dragIndex, hoverIndex); - - // Note: we're mutating the monitor item here! - // Generally it's better to avoid mutations, - // but it's good here for the sake of performance - // to avoid expensive index searches. - // eslint-disable-next-line no-param-reassign - currentItem.index = hoverIndex; - }; - -export { hoverSortable }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/index.ts b/app/react/V2/Components/Layouts/DragAndDrop/index.ts deleted file mode 100644 index 5f6a7f9c4c..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { DropZone } from './DropZone'; -export type { IItemComponentProps } from './Container'; -export { Container } from './Container'; -export { DraggableItem } from './DraggableItem'; -export { DragSource } from './DragSource'; -export { useDnDContext } from './useDnDContext'; -export type { IDnDContext } from './DnDDefinitions'; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx b/app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx deleted file mode 100644 index e2c0627dab..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx +++ /dev/null @@ -1,277 +0,0 @@ -/* eslint-disable max-statements */ -import React from 'react'; -import 'cypress-axe'; -import { mount } from '@cypress/react18'; -import { composeStories } from '@storybook/react'; -import * as stories from 'app/stories/DragAndDrop.stories'; - -const { Basic, WithItemComponent, Nested, Form } = composeStories(stories); - -describe('DragAndDrop', () => { - it('should be accessible', () => { - cy.checkAccessibility([, , ]); - }); - - const shouldContainListItems = (selector: string, items: string[]) => { - cy.get(selector) - .eq(0) - .within(() => { - cy.get('ul > li') - .should('have.length', items.length) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', items, { timeout: 200 }); - }); - }; - - const dragItem = (name: string, target: string = 'Drag items here') => { - cy.contains(name).trigger('dragstart'); - cy.contains(name).trigger('dragleave'); - cy.contains(target).trigger('drop'); - cy.contains(target).trigger('dragend'); - }; - - describe('Basic', () => { - beforeEach(() => { - mount(); - }); - - it('should list the active items', () => { - shouldContainListItems('div[data-testid="active-bin"]', ['Item 1', 'Item 2', 'Item 3']); - shouldContainListItems('div[data-testid="available-bin"]', ['Item 4', 'Item 5']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 1', 'Item 2', 'Item 3']); - }); - - it('should drag and drop a new item', () => { - dragItem('Item 4'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1', - 'Item 2', - 'Item 3', - 'Item 4', - ]); - shouldContainListItems('div[data-testid="available-bin"]', ['Item 5']); - shouldContainListItems('div[data-testid="state-bin"]', [ - 'Item 1', - 'Item 2', - 'Item 3', - 'Item 4', - ]); - }); - it('should sort the active items from top to down', () => { - cy.get('[data-testid="root-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-2"]', - { target: { x: 100, y: 18 } } - ); - shouldContainListItems('[data-testid="active-bin"]', ['Item 2', 'Item 3', 'Item 1']); - shouldContainListItems('[data-testid="available-bin"]', ['Item 4', 'Item 5']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 2', 'Item 3', 'Item 1']); - }); - - it('should sort the active items from down to top', () => { - cy.get('[data-testid="root-draggable-item-1"]').drag( - '[data-testid="root-draggable-item-0"]', - { target: { x: 100, y: 1 } } - ); - shouldContainListItems('[data-testid="active-bin"]', ['Item 2', 'Item 1', 'Item 3']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 2', 'Item 1', 'Item 3']); - }); - }); - - describe('Item Component', () => { - beforeEach(() => { - mount(); - }); - - it('should remove an active item', () => { - cy.contains('Item 2') - .parent() - .within(() => { - cy.get('button').click(); - }); - dragItem('Item 5'); - shouldContainListItems('div[data-testid="active-bin"]', ['Item 1', 'Item 3', 'Item 5']); - shouldContainListItems('div[data-testid="available-bin"]', ['Item 4', 'Item 2']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 1', 'Item 3', 'Item 5']); - }); - }); - - describe('Nested Component', () => { - beforeEach(() => { - mount(); - }); - - it('should list nested items', () => { - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - it('should add an Item as a parent', () => { - cy.get('[data-testid="available-draggable-item-0"]').drag('div[data-testid="root"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Item 4\nDRAG ITEMS HERE', - ]); - }); - - it('should add an Item as a child', () => { - cy.get('[data-testid="available-draggable-item-0"]').drag('div[data-testid="group_Item 1"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nItem 4\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 4', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should move an item from a parent to another parent', () => { - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag( - 'div[data-testid="group_Item 2"]' - ); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nDRAG ITEMS HERE', - 'Item 2\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should move an child item to the root level', () => { - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag('div[data-testid="root"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nDRAG ITEMS HERE', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Subitem 1\nDRAG ITEMS HERE', - ]); - }); - - it('should remove a parent', () => { - cy.get('[data-testid="root-draggable-item-0"]').within(() => { - cy.get('button').eq(0).click(); - }); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should add a released item', () => { - cy.get('[data-testid="root-draggable-item-0"]').within(() => { - cy.get('button').eq(0).click(); - }); - cy.get('[data-testid="available-draggable-item-2"]').drag('div[data-testid="root"]'); - - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Item 1\nDRAG ITEMS HERE', - ]); - }); - - it('should remove a child', () => { - cy.get('[data-testid="group_Item 1-draggable-item-0"]').within(() => { - cy.get('button').eq(0).click(); - }); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nDRAG ITEMS HERE', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should sort parents', () => { - cy.get('[data-testid="root-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-2"]', - { target: { x: 15, y: 135 } } - ); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - ]); - }); - - it('should sort children of a parent', () => { - cy.get('[data-testid="available-draggable-item-1"]').drag('[data-testid="group_Item 1"]'); - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag( - '[data-testid="group_Item 1-draggable-item-1"]', - { - target: { x: 100, y: 30 }, - } - ); - cy.get('[data-testid="root-draggable-item-2"]').drag('div[data-testid="group_Item 1"]', { - target: { x: 100, y: 30 }, - }); - cy.get('[data-testid="available-draggable-item-0"]').drag( - '[data-testid="group_Item 1"]>span', - { - target: { x: 100, y: 10 }, - } - ); - cy.get('[data-testid="group_Item 1-draggable-item-3"]').drag( - '[data-testid="group_Item 1-draggable-item-1"]' - ); - cy.get('[data-testid="group_Item 1-draggable-item-2"]').drag( - '[data-testid="group_Item 1-draggable-item-3"]', - { - target: { x: 100, y: 30 }, - } - ); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nItem 5\nItem 4\nItem 3\nSubitem 1\nDRAG ITEMS HERE', - 'Item 5', - 'Item 4', - 'Item 3', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - ]); - }); - - it('should not change list when dragging is not valid', () => { - cy.get('[data-testid="root-draggable-item-0"]').drag('[data-testid="group_Item 1"]>span', { - target: { x: 100, y: 15 }, - }); - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag( - '[data-testid="group_Item 1"]>span', - { - target: { x: 100, y: 15 }, - } - ); - cy.get('[data-testid="root-draggable-item-1"]').drag('div[data-testid="root"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - }); - - describe('Form', () => { - beforeEach(() => { - mount(
); - }); - - it('should update the state of a modified item', () => { - cy.get('[data-testid="root-draggable-item-0"]').within(() => cy.get('input').type(' ALL')); - cy.get('[data-testid="root-draggable-item-1"]').within(() => cy.get('input').type(' VALUES')); - cy.get('[data-testid="root-draggable-item-2"]').within(() => - cy.get('input').type(' WERE UPDATED', { delay: 150 }) - ); - cy.get('div[data-testid="state-bin"]').contains('WERE UPDATED'); - shouldContainListItems('div[data-testid="state-bin"]', [ - 'Item 1 ALL', - 'Item 2 VALUES', - 'Item 3 WERE UPDATED', - ]); - }); - }); -}); diff --git a/app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx b/app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx deleted file mode 100644 index fc858a45c9..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { useEffect, useState } from 'react'; -import update from 'immutability-helper'; -import type { IDraggable } from 'app/V2/shared/types'; -import { ItemTypes } from 'app/V2/shared/types'; -import { omit } from 'lodash'; -import type { IDnDContext, IDnDOperations } from './DnDDefinitions'; -import { - addActiveItem, - removeActiveItem, - mapWithID, - mapWithParent, - sortActiveItems, -} from './DnDDefinitions'; - -/* eslint-disable comma-spacing */ -const useDnDContext = ( - type: ItemTypes, - operations: IDnDOperations, - initialItems: T[] = [], - sourceItems: IDraggable[] = [] -) => { - const [activeItems, setActiveItems] = useState[]>( - mapWithParent( - initialItems, - undefined, - operations.itemsProperty, - operations.allowEditGroupsWithDnD - ) - ); - const [internalChange, setInternalChange] = useState(false); - const [dragging, setDragging] = useState(false); - - const [availableItems, setAvailableItems] = useState[]>( - mapWithID(sourceItems || []) - ); - const itemsProperty = operations.itemsProperty || 'items'; - - const updateItem = (item: IDraggable) => { - setInternalChange(true); - setActiveItems((prevActiveItems: IDraggable[]) => { - const index = prevActiveItems.findIndex(x => x.dndId === item.dndId); - return update(prevActiveItems, { - [index]: { - $set: item, - }, - }); - }); - }; - - useEffect(() => { - if (internalChange === true && !dragging && operations.onChange !== undefined) { - const sortedItems = activeItems - .filter(item => item) - .map(item => { - const values = item.value.items - ? item.value.items.map(subItem => - omit(subItem.value, ['dndId', 'items', operations.itemsProperty || '']) - ) - : []; - return { - ...omit(item.value, ['items', 'dndId', operations.itemsProperty || '']), - ...(operations.itemsProperty && values.length > 0 - ? { [operations.itemsProperty]: values } - : {}), - } as T; - }); - operations.onChange(sortedItems); - } else { - setInternalChange(true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeItems, dragging]); - - const state = { - activeItems, - setActiveItems, - availableItems, - setAvailableItems, - itemsProperty, - operations, - }; - const dndContext: IDnDContext = { - type, - addItem: addActiveItem(state), - removeItem: removeActiveItem(state), - sort: sortActiveItems(state), - updateItem, - updateActiveItems: (items: T[]) => { - const updatedItems = mapWithParent(items, undefined, itemsProperty); - setActiveItems(updatedItems); - setInternalChange(false); - return updatedItems; - }, - activeItems, - availableItems, - getDisplayName: operations.getDisplayName, - itemsProperty, - operations, - setDragging, - }; - return dndContext; -}; - -export { useDnDContext }; diff --git a/app/react/V2/Components/UI/TableV2/DnDComponents.tsx b/app/react/V2/Components/UI/Table/DnDComponents.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/DnDComponents.tsx rename to app/react/V2/Components/UI/Table/DnDComponents.tsx diff --git a/app/react/V2/Components/UI/Table/DraggableRow.tsx b/app/react/V2/Components/UI/Table/DraggableRow.tsx deleted file mode 100644 index c541493878..0000000000 --- a/app/react/V2/Components/UI/Table/DraggableRow.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { PropsWithChildren, RefObject } from 'react'; -import { Row } from '@tanstack/react-table'; -import { IDraggable } from 'app/V2/shared/types'; -import { DraggableItem, DropZone, type IDnDContext } from '../../Layouts/DragAndDrop'; -import { GrabDoubleIcon } from '../../CustomIcons'; - -interface GrabIconProps extends PropsWithChildren { - row: Row; - dndContext: IDnDContext; - previewRef?: RefObject; - item: IDraggable; - highLightGroups?: boolean; - subRowsKey?: string; -} - -interface RowWrapperProps extends PropsWithChildren { - row: Row; - className?: string; - draggableRow?: boolean; - dndContext?: IDnDContext; - innerRef?: RefObject; -} - -// eslint-disable-next-line comma-spacing -const GrabIcon = ({ - dndContext, - row, - previewRef, - item, - subRowsKey, - highLightGroups = true, -}: GrabIconProps) => { - const grabIconColor = - row.getIsExpanded() || - (highLightGroups && row.getCanExpand()) || - (subRowsKey && highLightGroups && Array.isArray((row.original as any)[subRowsKey])) || - row.depth > 0 - ? 'rgb(199 210 254)' - : 'rgb(224 231 255)'; - return ( - - - - ); -}; - -const RowWrapper = - // eslint-disable-next-line comma-spacing - ({ children, dndContext, row, draggableRow, className, innerRef }: RowWrapperProps) => { - if (!draggableRow || !dndContext) { - return ( - - {children} - - ); - } - const notParent = !row.parentId && row.getLeafRows().length === 0; - const parentItem = dndContext.activeItems.find(item => item.dndId === row.parentId); - return ( - - {children} - - ); - }; - -export { RowWrapper, GrabIcon }; diff --git a/app/react/V2/Components/UI/TableV2/GroupComponents.tsx b/app/react/V2/Components/UI/Table/GroupComponents.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/GroupComponents.tsx rename to app/react/V2/Components/UI/Table/GroupComponents.tsx diff --git a/app/react/V2/Components/UI/TableV2/RowSelectComponents.tsx b/app/react/V2/Components/UI/Table/RowSelectComponents.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/RowSelectComponents.tsx rename to app/react/V2/Components/UI/Table/RowSelectComponents.tsx diff --git a/app/react/V2/Components/UI/TableV2/SortingChevrons.tsx b/app/react/V2/Components/UI/Table/SortingChevrons.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/SortingChevrons.tsx rename to app/react/V2/Components/UI/Table/SortingChevrons.tsx diff --git a/app/react/V2/Components/UI/Table/Table.tsx b/app/react/V2/Components/UI/Table/Table.tsx index 08d9634864..e02262f5f7 100644 --- a/app/react/V2/Components/UI/Table/Table.tsx +++ b/app/react/V2/Components/UI/Table/Table.tsx @@ -1,159 +1,250 @@ import React, { useEffect, useMemo, useState } from 'react'; import { - getSortedRowModel, - getCoreRowModel, useReactTable, - SortingState, + getCoreRowModel, + ColumnDef, + flexRender, getExpandedRowModel, + SortingState, + getSortedRowModel, + RowSelectionState, } from '@tanstack/react-table'; -import { useIsFirstRender } from 'app/V2/CustomHooks'; -import { TableProps, CheckBoxHeader, CheckBoxCell } from './TableElements'; -import { TableHeader } from './TableHeader'; -import { TableBody } from './TableBody'; - -const applyForSelection = ( - withSelection: any, - withOutSelection: any, - enableSelection: boolean = false -) => (enableSelection ? withSelection : withOutSelection); - -// eslint-disable-next-line comma-spacing, max-statements -const Table = ({ +import { + DragEndEvent, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + DndContext, + closestCenter, +} from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import { DraggableRow, RowDragHandleCell, DnDHeader } from './DnDComponents'; +import { IndeterminateCheckboxHeader, IndeterminateCheckboxRow } from './RowSelectComponents'; +import { dndSortHandler, getRowIds } from './helpers'; +import { SortingChevrons } from './SortingChevrons'; +import { GroupCell, GroupHeader } from './GroupComponents'; + +type TableRow = { + rowId: string; + disableRowSelection?: boolean; + subRows?: (T & { rowId: string; disableRowSelection?: boolean })[]; +}; + +type TableProps> = { + columns: ColumnDef[]; + data: T[]; + onChange?: ({ + rows, + selectedRows, + sortingState, + }: { + rows: T[]; + selectedRows: RowSelectionState; + sortingState: SortingState; + }) => void; + dnd?: { enable?: boolean; disableEditingGroups?: boolean }; + enableSelections?: boolean; + defaultSorting?: SortingState; + sortingFn?: (sorting: SortingState) => void; + header?: React.ReactNode; + footer?: React.ReactNode; + className?: string; +}; + +const Table = >({ columns, data, - title, + onChange, + dnd, + enableSelections, + defaultSorting, + sortingFn, + header, footer, - initialState, - enableSelection, - sorting, - setSorting, - onSelection, - subRowsKey, - draggableRows = false, - allowEditGroupsWithDnD = true, - highLightGroups = true, - onChange = () => {}, + className, }: TableProps) => { - const manualSorting = Boolean(setSorting); - const [internalSorting, setInternalSortingSorting] = useState( - initialState?.sorting || [] - ); - const [rowSelection, setRowSelection] = useState({}); - const [internalData, setInternalData] = useState(data); - const [sortedChanged, setSortedChanged] = useState(false); - const isFirstRender = useIsFirstRender(); + const [dataState, setDataState] = useState(data); + const [rowSelection, setRowSelection] = useState({}); + const [sortingState, setSortingState] = useState(defaultSorting || []); - useEffect(() => { - setRowSelection({}); - setInternalData(data); - }, [data]); + const rowIds = useMemo(() => getRowIds(dataState), [dataState]); + const { memoizedColumns, groupColumnIndex } = useMemo<{ + memoizedColumns: ColumnDef[]; + groupColumnIndex: number; + // eslint-disable-next-line max-statements + }>(() => { + const tableColumns = [...columns]; + const hasGroups = data.find(item => item.subRows); + let calculatedIndex = 0; - const memoizedColumns = useMemo( - () => [ - ...applyForSelection( - [ - { - ...{ - id: 'checkbox-select', - header: CheckBoxHeader, - cell: CheckBoxCell, - }, - }, - ], - [], - enableSelection - ), - ...columns, - ], - [columns, enableSelection] - ); + if (hasGroups) { + tableColumns.unshift({ + id: 'group-button', + cell: GroupCell, + header: GroupHeader, + meta: { headerClassName: 'w-0' }, + }); + } - const sortingState = manualSorting ? sorting : internalSorting; - const sortingFunction = manualSorting ? setSorting : setInternalSortingSorting; + if (enableSelections) { + calculatedIndex += 1; + tableColumns.unshift({ + id: 'select', + header: IndeterminateCheckboxHeader, + cell: IndeterminateCheckboxRow, + meta: { headerClassName: 'w-0' }, + }); + } + + if (dnd?.enable) { + calculatedIndex += 1; + tableColumns.unshift({ + id: 'drag-handle', + cell: RowDragHandleCell, + header: DnDHeader, + meta: { headerClassName: 'w-0' }, + }); + } + + return { + memoizedColumns: tableColumns, + groupColumnIndex: calculatedIndex, + }; + }, [columns, data, enableSelections, dnd]); const table = useReactTable({ + data: dataState, columns: memoizedColumns, - manualSorting, - data: internalData, - initialState, state: { sorting: sortingState, - ...applyForSelection({ rowSelection }, {}, enableSelection), + ...(rowSelection && { rowSelection }), }, - enableRowSelection: (row: any) => - Boolean(enableSelection && !row.original?.disableRowSelection), - enableSubRowSelection: false, - onRowSelectionChange: applyForSelection(setRowSelection, () => undefined, enableSelection), - onSortingChange: sortingFunction, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getExpandedRowModel: getExpandedRowModel(), - getSubRows: (row: any) => { - if (subRowsKey) { - return row[subRowsKey]; - } - return []; - }, + manualSorting: Boolean(sortingFn), + onSortingChange: setSortingState, + getRowId: row => row.rowId, + getSubRows: row => row.subRows || undefined, + ...(enableSelections && { + //There seems to be a problem with react table types when using a function, typing as any + //fixes the issue + enableRowSelection: (row: any) => row.original.disableRowSelection !== true, + onRowSelectionChange: setRowSelection, + }), }); useEffect(() => { - if (isFirstRender) { - return; - } + setDataState(data); + setRowSelection({}); + }, [data]); - const sorted = table - .getRowModel() - .rows.filter(row => !row.parentId) - .map(row => row.original); - onChange(sorted); - setSortedChanged(false); + useEffect(() => { + if (onChange) { + if (sortingState.length) { + const sortedState = table.getSortedRowModel().rows.map(row => row.original); + onChange({ rows: sortedState, selectedRows: rowSelection, sortingState }); + } else { + onChange({ rows: dataState, selectedRows: rowSelection, sortingState }); + } + } + // 'onChange' and 'table' removed from deps to avoid infinite rerenders // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortingState]); + }, [dataState, rowSelection, sortingState]); useEffect(() => { - const selectedRows = table.getSelectedRowModel().flatRows; - if (onSelection) { - onSelection(selectedRows); + if (sortingFn) { + sortingFn(sortingState); } - }, [onSelection, rowSelection, table]); + }, [sortingFn, sortingState]); - const handleOnChange = (changedItems: T[]) => { - setSortedChanged(true); - onChange(changedItems); + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (active && over && active.id !== over.id) { + setDataState(() => { + let tableRows = dataState; + if (sortingState.length) { + table.resetSorting(); + tableRows = table.getSortedRowModel().rows.map(row => row.original); + } + return dndSortHandler({ + currentState: tableRows, + dataIds: rowIds, + activeId: active.id, + overId: over.id, + disableEditingGroups: dnd?.disableEditingGroups, + }); + }); + } }; + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ); + return ( -
- - {title && ( - - )} - - - {table.getHeaderGroups().map(headerGroup => ( - - ))} - - -
- {title} -
- {footer &&
{footer}
} -
+ +
+ + {header && } + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(hdr => { + const headerSorting = hdr.column.getCanSort(); + const customClassName = hdr.column.columnDef.meta?.headerClassName; + + return ( + + ); + })} + + ))} + + + + {table.getRowModel().rows.map(row => ( + + ))} + + +
{header}
+ + {flexRender(hdr.column.columnDef.header, hdr.getContext())} + {headerSorting && } + +
+ {footer &&
{footer}
} +
+
); }; +export type { TableProps, TableRow }; export { Table }; diff --git a/app/react/V2/Components/UI/Table/TableBody.tsx b/app/react/V2/Components/UI/Table/TableBody.tsx deleted file mode 100644 index 179348b970..0000000000 --- a/app/react/V2/Components/UI/Table/TableBody.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { PropsWithChildren } from 'react'; -import { Row } from '@tanstack/react-table'; -import { get } from 'lodash'; -import { ItemTypes } from 'app/V2/shared/types'; -import { TableRow } from './TableRow'; -import { withDnD, withDnDBackend } from '../../componentWrappers'; -import { useDnDContext } from '../../Layouts/DragAndDrop'; - -interface TableBodyProps extends PropsWithChildren { - draggableRows: boolean; - allowEditGroupsWithDnD?: boolean; - DndProvider?: React.FC; - HTML5Backend?: any; - items: any; - table: any; - onChange?: any; - subRowsKey?: string; - highLightGroups?: boolean; -} - -type TypeWithDnDId = T & { - dndId: string; -}; - -// eslint-disable-next-line comma-spacing -const setItemId = (item: T, parent: TypeWithDnDId | undefined, index: number) => ({ - ...item, - dndId: parent ? `${parent.dndId}.${index}` : index.toString(), -}); - -const setRowId: ( - subRowsKey: string, - records: T[], - parent?: TypeWithDnDId -) => TypeWithDnDId[] = (subRowsKey, records, parent) => - (records || []) - .filter(f => f) - .map((item, index) => { - const itemWithId = setItemId(item, parent, index); - return { - ...itemWithId, - ...(subRowsKey - ? { - [subRowsKey]: setRowId(subRowsKey, get(item, subRowsKey), itemWithId), - } - : {}), - }; - }); - -// eslint-disable-next-line comma-spacing -const TableBodyComponent = ({ - draggableRows, - allowEditGroupsWithDnD, - DndProvider, - HTML5Backend, - items, - table, - subRowsKey, - onChange, - highLightGroups = true, -}: TableBodyProps) => { - const dndContext = useDnDContext( - ItemTypes.ROW, - { - getDisplayName: item => item.dndId!, - itemsProperty: subRowsKey, - onChange, - allowEditGroupsWithDnD, - }, - items, - [] - ); - - return draggableRows && DndProvider && HTML5Backend ? ( - - - {dndContext.activeItems - .map(item => { - const itemValue = item.value as TypeWithDnDId; - const row = table.getRowModel().rowsById[itemValue.dndId]; - const children = - row && row.getIsExpanded() - ? (item.value.items || []) - .filter(v => v) - .map(subItem => { - const subItemValue = subItem.value as TypeWithDnDId; - const childRow = table.getRowModel().rowsById[subItemValue.dndId]; - - return childRow !== undefined ? ( - - ) : ( - childRow - ); - }) - .filter(child => child !== undefined) - : []; - return row !== undefined ? ( - - - {children} - - ) : ( - row - ); - }) - .filter(row => row !== undefined)} - - - ) : ( - - {table.getRowModel().rows.map((row: Row) => ( - - key={row.id} - row={row} - highLightGroups={highLightGroups} - subRowsKey={subRowsKey} - /> - ))} - - ); -}; - -const typedMemo: (c: T) => T = React.memo; - -const TableBody = (props: TableBodyProps) => { - const tableBodyProps = { ...props, items: setRowId(props.subRowsKey || 'items', props.items) }; - return withDnD(typedMemo(withDnDBackend(TableBodyComponent)))(tableBodyProps); -}; - -export { TableBody }; diff --git a/app/react/V2/Components/UI/Table/TableElements.tsx b/app/react/V2/Components/UI/Table/TableElements.tsx deleted file mode 100644 index 83e2b8c39c..0000000000 --- a/app/react/V2/Components/UI/Table/TableElements.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* eslint-disable react/jsx-props-no-spreading */ -/* eslint-disable react/no-multi-comp */ -import React, { HTMLProps, useEffect, useRef, Dispatch, SetStateAction } from 'react'; -import { - ColumnDef, - TableState, - Row, - Table as TableDef, - SortingState, - Header, -} from '@tanstack/react-table'; -import { ChevronUpDownIcon, ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/20/solid'; -import { t } from 'app/I18N'; - -interface TableProps { - columns: ColumnDef[]; - data: T[]; - title?: string | React.ReactNode; - footer?: string | React.ReactNode; - initialState?: Partial; - enableSelection?: boolean; - sorting?: SortingState; - setSorting?: Dispatch>; - onSelection?: Dispatch[]>>; - subRowsKey?: string; - draggableRows?: boolean; - allowEditGroupsWithDnD?: boolean; - highLightGroups?: boolean; - onChange?: (rows: T[]) => void; -} - -const IndeterminateCheckbox = ({ - indeterminate, - className = '', - id, - disabled, - ...rest -}: { indeterminate?: boolean } & HTMLProps) => { - const ref = useRef(null!); - - useEffect(() => { - if (typeof indeterminate === 'boolean') { - ref.current.indeterminate = !rest.checked && indeterminate; - } - }, [ref, indeterminate, rest.checked]); - - return ( - // eslint-disable-next-line react/jsx-props-no-spreading - - - ); -}; - -// eslint-disable-next-line comma-spacing -const getIcon = (header: Header, sortedChanged: boolean) => { - const sorting = !sortedChanged ? header.column.getIsSorted() : false; - const testId = `${header.id}_${sorting.toString()}`; - switch (sorting) { - case false: - return ; - case 'asc': - return ; - case 'desc': - default: - return ; - } -}; - -// eslint-disable-next-line comma-spacing -const CheckBoxHeader = ({ table }: { table: TableDef }) => ( - -); - -// eslint-disable-next-line comma-spacing -const CheckBoxCell = ({ row }: { row: Row }) => ( -
- { - row.getToggleSelectedHandler()(e); - row.subRows.forEach(subRow => subRow.getToggleSelectedHandler()(e)); - }, - id: row.id, - }} - /> -
-); - -export type { TableProps }; -export { CheckBoxHeader, CheckBoxCell, getIcon }; diff --git a/app/react/V2/Components/UI/Table/TableHeader.tsx b/app/react/V2/Components/UI/Table/TableHeader.tsx deleted file mode 100644 index 3875badaec..0000000000 --- a/app/react/V2/Components/UI/Table/TableHeader.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { HeaderGroup, flexRender } from '@tanstack/react-table'; -import { getIcon } from './TableElements'; - -interface RowProps { - headerGroup: HeaderGroup; - draggableRows: boolean; - sortedChanged: boolean; -} -/* eslint-disable comma-spacing */ -const TableHeader = ({ headerGroup, draggableRows, sortedChanged }: RowProps) => ( - - {headerGroup.headers.map(header => { - const isSortable = header.column.getCanSort(); - const isSelect = header.column.id === 'checkbox-select'; - const commonHeaderClassName = `${draggableRows ? 'pl-7' : ''} ${ - isSelect ? 'w-0 px-4 py-3' : 'px-6 py-3' - }`; - - const headerClassName = `${!header.column.columnDef.meta?.headerClassName?.includes('invisible') ? commonHeaderClassName : ''} ${header.column.columnDef.meta?.headerClassName || ''}`; - - return ( - -
- {flexRender(header.column.columnDef.header, header.getContext())} - {isSortable && getIcon(header, sortedChanged)} -
- - ); - })} - -); - -export { TableHeader }; diff --git a/app/react/V2/Components/UI/Table/TableRow.tsx b/app/react/V2/Components/UI/Table/TableRow.tsx deleted file mode 100644 index a9afbc6cd5..0000000000 --- a/app/react/V2/Components/UI/Table/TableRow.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { PropsWithChildren, useRef } from 'react'; -import { Row, flexRender } from '@tanstack/react-table'; -import { IDraggable } from 'app/V2/shared/types'; -import { type IDnDContext } from '../../Layouts/DragAndDrop'; -import { GrabIcon, RowWrapper } from './DraggableRow'; - -interface TableRowProps extends PropsWithChildren { - row: Row; - draggableRow?: boolean; - dndContext?: IDnDContext; - item?: IDraggable; - highLightGroups?: boolean; - subRowsKey?: string; -} - -/* eslint-disable comma-spacing */ -const TableRow = ({ - draggableRow, - row, - dndContext, - item, - subRowsKey, - highLightGroups = true, -}: TableRowProps) => { - const previewRef = useRef(null); - const icons = - draggableRow && dndContext && item - ? [ - , - ] - : []; - const isSubGroup = row.depth > 0; - const bg = - (row.getIsExpanded() && row.getCanExpand()) || - (highLightGroups && row.getCanExpand()) || - (subRowsKey && highLightGroups && Array.isArray((row.original as any)[subRowsKey])) - ? 'bg-primary-100' - : ''; - - return ( - - {row.getVisibleCells().map((cell, columnIndex) => { - const isSelect = cell.column.id === 'checkbox-select'; - const firstColumnClass = draggableRow && columnIndex === 0 ? 'flex items-center gap-3' : ''; - - let border = ''; - if (isSelect && isSubGroup) { - border = 'border-r-2 border-primary-300'; - } - - return ( - - {icons[columnIndex]} - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ); - })} - - ); -}; - -export { TableRow }; diff --git a/app/react/V2/Components/UI/TableV2/helpers.ts b/app/react/V2/Components/UI/Table/helpers.ts similarity index 100% rename from app/react/V2/Components/UI/TableV2/helpers.ts rename to app/react/V2/Components/UI/Table/helpers.ts diff --git a/app/react/V2/Components/UI/Table/index.ts b/app/react/V2/Components/UI/Table/index.ts new file mode 100644 index 0000000000..eec46d6266 --- /dev/null +++ b/app/react/V2/Components/UI/Table/index.ts @@ -0,0 +1,2 @@ +export { Table } from './Table'; +export type { TableProps, TableRow } from './Table'; diff --git a/app/react/V2/Components/UI/TableV2/specs/fixtures.ts b/app/react/V2/Components/UI/Table/specs/fixtures.ts similarity index 100% rename from app/react/V2/Components/UI/TableV2/specs/fixtures.ts rename to app/react/V2/Components/UI/Table/specs/fixtures.ts diff --git a/app/react/V2/Components/UI/TableV2/specs/helpers.spec.ts b/app/react/V2/Components/UI/Table/specs/helpers.spec.ts similarity index 100% rename from app/react/V2/Components/UI/TableV2/specs/helpers.spec.ts rename to app/react/V2/Components/UI/Table/specs/helpers.spec.ts diff --git a/app/react/V2/Components/UI/TableV2/Table.tsx b/app/react/V2/Components/UI/TableV2/Table.tsx deleted file mode 100644 index e02262f5f7..0000000000 --- a/app/react/V2/Components/UI/TableV2/Table.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { - useReactTable, - getCoreRowModel, - ColumnDef, - flexRender, - getExpandedRowModel, - SortingState, - getSortedRowModel, - RowSelectionState, -} from '@tanstack/react-table'; -import { - DragEndEvent, - KeyboardSensor, - MouseSensor, - TouchSensor, - useSensor, - useSensors, - DndContext, - closestCenter, -} from '@dnd-kit/core'; -import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; -import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; -import { DraggableRow, RowDragHandleCell, DnDHeader } from './DnDComponents'; -import { IndeterminateCheckboxHeader, IndeterminateCheckboxRow } from './RowSelectComponents'; -import { dndSortHandler, getRowIds } from './helpers'; -import { SortingChevrons } from './SortingChevrons'; -import { GroupCell, GroupHeader } from './GroupComponents'; - -type TableRow = { - rowId: string; - disableRowSelection?: boolean; - subRows?: (T & { rowId: string; disableRowSelection?: boolean })[]; -}; - -type TableProps> = { - columns: ColumnDef[]; - data: T[]; - onChange?: ({ - rows, - selectedRows, - sortingState, - }: { - rows: T[]; - selectedRows: RowSelectionState; - sortingState: SortingState; - }) => void; - dnd?: { enable?: boolean; disableEditingGroups?: boolean }; - enableSelections?: boolean; - defaultSorting?: SortingState; - sortingFn?: (sorting: SortingState) => void; - header?: React.ReactNode; - footer?: React.ReactNode; - className?: string; -}; - -const Table = >({ - columns, - data, - onChange, - dnd, - enableSelections, - defaultSorting, - sortingFn, - header, - footer, - className, -}: TableProps) => { - const [dataState, setDataState] = useState(data); - const [rowSelection, setRowSelection] = useState({}); - const [sortingState, setSortingState] = useState(defaultSorting || []); - - const rowIds = useMemo(() => getRowIds(dataState), [dataState]); - const { memoizedColumns, groupColumnIndex } = useMemo<{ - memoizedColumns: ColumnDef[]; - groupColumnIndex: number; - // eslint-disable-next-line max-statements - }>(() => { - const tableColumns = [...columns]; - const hasGroups = data.find(item => item.subRows); - let calculatedIndex = 0; - - if (hasGroups) { - tableColumns.unshift({ - id: 'group-button', - cell: GroupCell, - header: GroupHeader, - meta: { headerClassName: 'w-0' }, - }); - } - - if (enableSelections) { - calculatedIndex += 1; - tableColumns.unshift({ - id: 'select', - header: IndeterminateCheckboxHeader, - cell: IndeterminateCheckboxRow, - meta: { headerClassName: 'w-0' }, - }); - } - - if (dnd?.enable) { - calculatedIndex += 1; - tableColumns.unshift({ - id: 'drag-handle', - cell: RowDragHandleCell, - header: DnDHeader, - meta: { headerClassName: 'w-0' }, - }); - } - - return { - memoizedColumns: tableColumns, - groupColumnIndex: calculatedIndex, - }; - }, [columns, data, enableSelections, dnd]); - - const table = useReactTable({ - data: dataState, - columns: memoizedColumns, - state: { - sorting: sortingState, - ...(rowSelection && { rowSelection }), - }, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getExpandedRowModel: getExpandedRowModel(), - manualSorting: Boolean(sortingFn), - onSortingChange: setSortingState, - getRowId: row => row.rowId, - getSubRows: row => row.subRows || undefined, - ...(enableSelections && { - //There seems to be a problem with react table types when using a function, typing as any - //fixes the issue - enableRowSelection: (row: any) => row.original.disableRowSelection !== true, - onRowSelectionChange: setRowSelection, - }), - }); - - useEffect(() => { - setDataState(data); - setRowSelection({}); - }, [data]); - - useEffect(() => { - if (onChange) { - if (sortingState.length) { - const sortedState = table.getSortedRowModel().rows.map(row => row.original); - onChange({ rows: sortedState, selectedRows: rowSelection, sortingState }); - } else { - onChange({ rows: dataState, selectedRows: rowSelection, sortingState }); - } - } - // 'onChange' and 'table' removed from deps to avoid infinite rerenders - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataState, rowSelection, sortingState]); - - useEffect(() => { - if (sortingFn) { - sortingFn(sortingState); - } - }, [sortingFn, sortingState]); - - const handleDragEnd = (event: DragEndEvent) => { - const { active, over } = event; - - if (active && over && active.id !== over.id) { - setDataState(() => { - let tableRows = dataState; - if (sortingState.length) { - table.resetSorting(); - tableRows = table.getSortedRowModel().rows.map(row => row.original); - } - return dndSortHandler({ - currentState: tableRows, - dataIds: rowIds, - activeId: active.id, - overId: over.id, - disableEditingGroups: dnd?.disableEditingGroups, - }); - }); - } - }; - - const sensors = useSensors( - useSensor(MouseSensor, {}), - useSensor(TouchSensor, {}), - useSensor(KeyboardSensor, {}) - ); - - return ( - -
- - {header && } - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(hdr => { - const headerSorting = hdr.column.getCanSort(); - const customClassName = hdr.column.columnDef.meta?.headerClassName; - - return ( - - ); - })} - - ))} - - - - {table.getRowModel().rows.map(row => ( - - ))} - - -
{header}
- - {flexRender(hdr.column.columnDef.header, hdr.getContext())} - {headerSorting && } - -
- {footer &&
{footer}
} -
-
- ); -}; - -export type { TableProps, TableRow }; -export { Table }; diff --git a/app/react/V2/Components/UI/index.ts b/app/react/V2/Components/UI/index.ts index 734d4f78b2..52d73142c6 100644 --- a/app/react/V2/Components/UI/index.ts +++ b/app/react/V2/Components/UI/index.ts @@ -2,7 +2,7 @@ export { Button } from './Button'; export { EmbededButton } from './EmbededButton'; export { Modal } from './Modal'; export { Pill } from './Pill'; -export { Table } from './TableV2/Table'; +export { Table } from './Table'; export { ToggleButton } from './ToggleButton'; export { NotificationsContainer } from './NotificationsContainer'; export { Tabs } from './Tabs'; @@ -17,10 +17,4 @@ export { MediaPlayer } from './MediaPlayer'; export { FileIcon } from './FileIcon'; export type { PillColor } from './Pill'; -export type { TableProps, TableRow } from './TableV2/Table'; - -//This component will be migrated to the new version of the table -// eslint-disable-next-line camelcase -export { Table as Table_deprecated } from './Table/Table'; -// eslint-disable-next-line camelcase -export type { TableProps as TableProps_deprecated } from './Table/TableElements'; +export type { TableProps, TableRow } from './Table'; diff --git a/app/react/V2/Components/UI/specs/Table.cy.tsx b/app/react/V2/Components/UI/specs/Table.cy.tsx index 2a6affaa15..0af68e6484 100644 --- a/app/react/V2/Components/UI/specs/Table.cy.tsx +++ b/app/react/V2/Components/UI/specs/Table.cy.tsx @@ -2,36 +2,35 @@ import React from 'react'; import 'cypress-axe'; import { mount } from '@cypress/react18'; -import { map } from 'lodash'; import { composeStories } from '@storybook/react'; +import { map } from 'lodash'; import * as stories from 'app/stories/Table.stories'; +import { tableWithDisabled } from '../Table/specs/fixtures'; -const { Basic, WithActions, WithCheckboxes, WithInitialState, WithDnD, NestedDnD } = - composeStories(stories); +const { Basic, Nested, Custom } = composeStories(stories); describe('Table', () => { - const data = Basic.args.data || []; + const data = Basic.args.tableData || []; + const dataWithNested = Nested.args.tableData || []; const checkRowContent = (rowNumber: number, cellsContent: string[]) => { - cellsContent.forEach((content, index) => - cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content) + cellsContent.forEach( + (content, index) => + content && + cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content) ); }; it('should be accessible', () => { cy.injectAxe(); - mount(); cy.checkA11y(); + }); - mount(); - cy.checkA11y(); - - mount(); - cy.checkA11y(); - - mount(); - cy.checkA11y(); + beforeEach(() => { + Basic.args.defaultSorting = undefined; + Basic.args.enableSelections = false; + Basic.args.dnd = { enable: false, disableEditingGroups: false }; }); it('Should return a table with the columns and row specified', () => { @@ -41,330 +40,645 @@ describe('Table', () => { checkRowContent(1, ['Entity 2', data[0].description, '2']); checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + checkRowContent(3, ['Entity 4', data[2].description, '4']); + checkRowContent(4, ['Entity 3', data[3].description, '3']); + checkRowContent(5, ['Entity 5', data[4].description, '5']); }); - it('Should sort the rows with the sorting state specified', () => { - mount(); - - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 3', data[2].description, '3']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); + it('should render the data in a custom component and styles', () => { + mount(); + cy.get('table').within(() => { + cy.contains('th', 'Description').should( + 'have.attr', + 'class', + 'p-4 text-sm text-gray-500 uppercase border-b bg-blue-700 text-white' + ); + cy.contains('td', 'Entity 2').should( + 'have.attr', + 'class', + 'relative px-4 py-2 bg-gray-100 text-red-700 ' + ); + cy.contains('td', 'Entity 1').should( + 'have.attr', + 'class', + 'relative px-4 py-2 bg-gray-100 text-red-700 ' + ); + cy.get('div[class="text-white bg-orange-500"]').should('have.length', 5); + cy.get('button').should('have.length', 5); + }); }); - it('should render the data in a custom component', () => { - mount(); - cy.get('tbody > :nth-child(1) > :nth-child(3) > div').should( - 'have.class', - 'text-center text-white bg-gray-400 rounded' - ); - }); + it('should trigger the custom action for the buttons', () => { + Custom.args.actionFn = cy.spy().as('actionSpy'); + mount(); - it('should render the header appending custom styles passed in the definition of the columns', () => { - mount(); - cy.get('table > thead > tr > th:nth-child(3)').should( - 'have.class', - 'px-6 py-3 w-1/4 bg-error-100 text-blue-600' - ); + cy.contains('tr', 'Entity 1').within(() => { + cy.contains('button', 'Action').realClick(); + }); + + cy.get('@actionSpy').should('have.been.calledOnceWith', 'A1'); }); describe('Sorting', () => { it('Should be sortable by title', () => { mount(); - cy.get('tr th').contains('Title').click(); + + cy.get('th').contains('Title').realClick(); + cy.contains('button', 'Save changes').realClick(); + checkRowContent(1, ['Entity 1', data[1].description, '1']); checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + checkRowContent(3, ['Entity 3', data[3].description, '3']); + checkRowContent(4, ['Entity 4', data[2].description, '4']); + checkRowContent(5, ['Entity 5', data[4].description, '5']); + }); + + it('should return to the default sorting', () => { + mount(); + cy.get('th').contains('Title').realClick().realClick().realClick(); + + checkRowContent(1, ['Entity 2', data[0].description, '2']); + checkRowContent(2, ['Entity 1', data[1].description, '1']); + checkRowContent(3, ['Entity 4', data[2].description, '4']); + checkRowContent(4, ['Entity 3', data[3].description, '3']); + checkRowContent(5, ['Entity 5', data[4].description, '5']); + }); + + it('should keep selections when sorting', () => { + Basic.args.enableSelections = true; + mount(); + + cy.get('tbody').within(() => { + cy.get('input[type="checkbox"]').eq(0).check(); + cy.get('input[type="checkbox"]').eq(2).check(); + }); + + cy.get('th').contains('Title').realClick().realClick(); + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 2'); + cy.contains('Entity 4'); + }); + }); + + it('should sort items in groups', () => { + mount(); + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + checkRowContent(1, [ + 'Drag row', + 'Select', + 'Group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(2, [ + 'Drag row', + 'Select', + '', + 'Sub 1-1', + dataWithNested[0].subRows[0].description, + '5', + ]); + checkRowContent(3, [ + 'Drag row', + 'Select', + '', + 'Sub 1-2', + dataWithNested[0].subRows[1].description, + '7', + ]); + + cy.get('th').contains('Title').realClick().realClick(); + + checkRowContent(6, [ + 'Drag row', + 'Select', + 'Group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(7, [ + 'Drag row', + 'Select', + '', + 'Sub 1-2', + dataWithNested[0].subRows[1].description, + '7', + ]); + checkRowContent(8, [ + 'Drag row', + 'Select', + '', + 'Sub 1-1', + dataWithNested[0].subRows[0].description, + '5', + ]); }); it('should disable sorting when defined in the columns', () => { - mount(); - cy.get('tr th').contains('Description').click(); - checkRowContent(1, ['Entity 2', '2', data[0].description]); + mount(); + cy.get('tr th').contains('Title').children().should('have.length', 1); + cy.get('tr th').contains('Description').children().should('have.length', 0); + cy.get('tr th').contains('Date added').children().should('have.length', 1); + + cy.get('th').contains('Description').realClick(); + checkRowContent(1, ['Entity 2', data[0].description, '2']); }); - it('should allow external control of sorting', () => { + it('Should sort the rows with the sorting state specified', () => { + Basic.args.defaultSorting = [{ id: 'created', desc: false }]; + mount(); + + checkRowContent(1, ['Entity 1', data[1].description, '1']); + checkRowContent(2, ['Entity 2', data[0].description, '2']); + checkRowContent(3, ['Entity 3', data[3].description, '3']); + }); + + it('should reset sorting state when using dnd after sorting', () => { + Basic.args.dnd = { enable: true }; + mount(); + + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + cy.get('th').contains('Title').realClick().realClick(); + cy.contains('Sorted by title'); + + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(6), + cy.get('button[aria-roledescription="sortable"]').eq(2) + ); + + cy.contains('No sorting'); + }); + + it('it should sort items within groups', () => { + Nested.args.dnd = { enable: false }; + Nested.args.enableSelections = false; + mount(); + + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + checkRowContent(1, ['Open group', 'Group 1', dataWithNested[0].description, '10']); + checkRowContent(2, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); + checkRowContent(3, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); + + cy.contains('th', 'Title').realClick().realClick(); + + checkRowContent(6, ['Open group', 'Group 1', dataWithNested[0].description, '10']); + checkRowContent(7, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); + checkRowContent(8, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); + }); + + it('should allow manually controlling the sorting', () => { const setSortingSpy = cy.stub().as('setSortingSpy'); + Basic.args.sortingFn = setSortingSpy; - mount(); - cy.get('tr th').contains('Title').click(); + mount(); + checkRowContent(1, ['Entity 2', data[0].description, '2']); + cy.get('th').contains('Title').realClick(); + checkRowContent(1, ['Entity 2', data[0].description, '2']); - cy.get('@setSortingSpy').should('have.been.calledOnce'); + cy.get('@setSortingSpy').should('have.been.calledTwice'); + cy.get('@setSortingSpy').should('have.been.calledWith', []); + cy.get('@setSortingSpy').should('have.been.calledWith', [{ id: 'title', desc: false }]); }); }); describe('Selections', () => { - it('should select items from each table', () => { - mount(); - cy.contains('Short text'); - cy.get('[data-testid="table"]').eq(0).get('thead > tr > th').eq(0).click(); - - cy.get('tbody') - .eq(1) - .within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); + beforeEach(() => { + Basic.args.enableSelections = true; + Basic.args.dnd = { enable: true }; + mount(); + }); - cy.contains('p', 'Selected items for Table A: 3'); - cy.contains('p', 'Selections of Table A: Entity 2, Entity 1, Entity 3,'); - cy.contains('p', 'Selected items for Table B: 2'); - cy.contains('p', 'Selections of Table B: Entity 2, Entity 3,'); + it('should select and unselect some items selections', () => { + cy.contains('Select all').realClick(); + cy.get('tbody').within(() => { + cy.get('input[type="checkbox"]').eq(0).uncheck(); + cy.get('input[type="checkbox"]').eq(2).uncheck(); + }); + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 1'); + cy.contains('Entity 2').should('not.exist'); + cy.contains('Entity 3'); + cy.contains('Entity 4').should('not.exist'); + cy.contains('Entity 5'); + }); }); - it('should clear selected items when data changes', () => { - mount(); - cy.contains('Short text'); - cy.get('[data-testid="table"]').eq(0).get('thead > tr > th').eq(0).click(); + it('should reset selections when adding a new entry to the table', () => { + cy.contains('Select all').realClick(); + cy.contains('button', 'Save changes').realClick(); + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 1'); + cy.contains('Entity 2'); + cy.contains('Entity 3'); + cy.contains('Entity 4'); + cy.contains('Entity 5'); + }); + cy.get('#checkbox-header').should('be.checked'); - cy.get('tbody') - .eq(1) - .within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); + cy.contains('button', 'Add new item').realClick(); + cy.get('#checkbox-header').should('not.be.checked'); + }); - cy.contains('button', 'Update table data').click(); + it('should reset selections when removing an item from the table', () => { + cy.contains('Select all').realClick(); + cy.contains('button', 'Save changes').realClick(); + cy.get('#checkbox-header').should('be.checked'); + cy.contains('button', 'Remove last item').realClick(); + cy.get('#checkbox-header').should('not.be.checked'); + }); - cy.contains('p', 'Selections of Table A: Entity 2, Entity 1, Entity 3,'); - cy.contains('p', 'Selections of Table B: Entity 2, Entity 3,').should('not.exist'); + it('should not select items with disabled row selection', () => { + Nested.args.enableSelections = true; + Nested.args.dnd = { enable: true }; + Nested.args.tableData = tableWithDisabled; + mount(); + cy.contains('Select all').realClick(); + cy.contains('button', 'Save changes').realClick(); + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Group 1'); + cy.contains('Group 2'); + cy.contains('Item 1').should('not.exist'); + cy.contains('Item 2'); + }); + cy.get('[data-testid="selected-subrows"]').within(() => { + cy.contains('Sub 1-1').should('not.exist'); + cy.contains('Sub 1-2'); + cy.contains('Sub 1-3'); + }); }); - it('should not clear selections if data is not changed', () => { - mount(); - cy.contains('Short text'); - cy.get('[data-testid="table"]') - .eq(1) - .within(() => cy.get('thead > tr > th').eq(0).click()); + it('should change parent status based on selected children', () => { + Nested.args.enableSelections = true; + Nested.args.dnd = { enable: true }; + Nested.args.tableData = tableWithDisabled; + mount(); - cy.contains('button', 'Reset table data').click(); - cy.contains('p', 'Selections of Table B: Entity 2, Entity 1, Entity 3,'); + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + cy.get('input').realClick().should('be.checked'); + }); + cy.contains('tr', 'Sub 1-1').within(() => { + cy.get('input').should('not.be.checked'); + }); + cy.contains('tr', 'Sub 1-2').within(() => { + cy.get('input').should('be.checked'); + cy.get('input').realClick(); + }); + cy.contains('tr', 'Sub 1-3').within(() => { + cy.get('input').should('be.checked'); + cy.get('input').realClick(); + }); + cy.contains('tr', 'Group 1').within(() => { + cy.get('input').should('be.checked'); + }); }); }); describe('DnD', () => { + beforeEach(() => { + Basic.args.dnd = { enable: true }; + Basic.args.enableSelections = true; + Nested.args.tableData = dataWithNested; + mount(); + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Entity 2 Entity 1 Entity 4 Entity 3 Entity 5'); + }); + }); + it('should sort rows by dragging', () => { - mount(); - cy.get('[data-testid="update_items"] > ul > li').should('have.length', 0); - cy.get('[data-testid="description_desc"]').should('have.length', 1); - - cy.get('[data-testid="root-draggable-item-2"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(1), + cy.get('button[aria-roledescription="sortable"]').eq(0) ); - cy.get('[data-testid="description_false"]').should('have.length', 1); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(3), + cy.get('button[aria-roledescription="sortable"]').eq(2) + ); - checkRowContent(1, ['Entity 3', data[2].description, '3']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); + checkRowContent(1, ['Drag row', 'Select', 'Entity 1', data[1].description, '1']); + checkRowContent(2, ['Drag row', 'Select', 'Entity 2', data[0].description, '2']); + checkRowContent(3, ['Drag row', 'Select', 'Entity 3', data[3].description, '3']); + checkRowContent(4, ['Drag row', 'Select', 'Entity 4', data[2].description, '4']); + checkRowContent(5, ['Drag row', 'Select', 'Entity 5', data[4].description, '5']); - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 3) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 3', 'Entity 2', 'Entity 1']); + cy.contains('button', 'Save changes').realClick(); + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Entity 1 Entity 2 Entity 3 Entity 4 Entity 5'); + }); }); - it('should sort rows by header', () => { - mount(); + it('should keep selections while dragging', () => { + cy.get('tbody').within(() => { + cy.get('input[type="checkbox"]').eq(0).check(); + cy.get('input[type="checkbox"]').eq(2).check(); + }); - cy.get('[data-testid="root-draggable-item-2"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(1), + cy.get('button[aria-roledescription="sortable"]').eq(0) ); - cy.get('[data-testid="title_false"]').click(); + cy.contains('button', 'Save changes').realClick(); - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 1').should('not.exist'); + cy.contains('Entity 2'); + cy.contains('Entity 4'); + cy.contains('Entity 3').should('not.exist'); + cy.contains('Entity 5').should('not.exist'); + }); - cy.get('[data-testid="title_asc"]').should('have.length', 1); + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Entity 1 Entity 2 Entity 4 Entity 3 Entity 5'); + }); + }); + }); - cy.get('[data-testid="root-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-2"]', - { - target: { x: 5, y: 20 }, - force: true, - } - ); + describe('Nested data', () => { + beforeEach(() => { + Nested.args.dnd = { enable: true }; + Nested.args.enableSelections = true; + mount(); + }); + + it('should check the content', () => { + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Group 1 Group 2 Group 3 Group 4 Item 1 Item 2'); + }); + checkRowContent(1, [ + 'Drag row', + 'Select', + 'Group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(2, [ + 'Drag row', + 'Select', + 'Group', + 'Group 2', + dataWithNested[1].description, + '20', + ]); + checkRowContent(3, [ + 'Drag row', + 'Select', + 'Group', + 'Group 3', + dataWithNested[2].description, + '30', + ]); + checkRowContent(4, [ + 'Drag row', + 'Select', + 'Group', + 'Group 4', + dataWithNested[3].description, + '40', + ]); + checkRowContent(5, [ + 'Drag row', + 'Select', + undefined, + 'Item 1', + dataWithNested[4].description, + '50', + ]); + checkRowContent(6, [ + 'Drag row', + 'Select', + undefined, + 'Item 2', + dataWithNested[5].description, + '60', + ]); + }); - cy.get('[data-testid="title_false"]').should('have.length', 1); + it('should expand groups and check for accessibility', () => { + cy.get('tbody').within(() => { + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('tr', 'Group 2').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('tr', 'Group 3').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('td', 'Sub 1-1'); + cy.contains('td', 'Sub 1-2'); + cy.contains('td', 'Sub 2-1'); + cy.contains('td', 'Sub 2-2'); + cy.contains('td', 'Sub 3-1'); + cy.contains('td', 'Sub 1-2'); + }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 3', data[2].description, '3']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); + cy.checkA11y(); }); - }); - describe('Nested DnD', () => { - it('should render children as subRows', () => { - mount(); + it('should sort children element with dnd', () => { + cy.get('tbody').within(() => { + cy.contains('tr', 'Group 1').within(() => { + cy.contains('Open group').realClick(); + }); + cy.contains('tr', 'Group 3').within(() => { + cy.contains('Open group').realClick(); + }); + cy.contains('td', 'Sub 1-1'); + cy.contains('td', 'Sub 1-2'); + cy.contains('td', 'Sub 3-1'); + cy.contains('td', 'Sub 3-2'); + }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(5), + cy.get('button[aria-roledescription="sortable"]').eq(1) + ); - cy.get('[data-testid="update_items"] > ul > li').should('have.length', 0); + checkRowContent(1, [ + 'Drag row', + 'Select', + 'Open group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(2, [ + 'Drag row', + 'Select', + undefined, + 'Sub 3-1', + dataWithNested[2].subRows[0].description, + '12', + ]); + + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="sorted-subrows"] > .flex > :nth-child(1)').contains( + '|Group 1 - Sub 3-1|' + ); }); - it('should expand a group', () => { - mount(); - - cy.contains('children').click(); + it('should add an item to an empty group', () => { + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('tr', 'Group 3').within(() => { + cy.contains('button', 'Open group').realClick(); + }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(4, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(3), + cy.get('td').contains('Empty group. Drop here to add') + ); - cy.get('[data-testid="update_items"] > ul > li').should('have.length', 0); + checkRowContent(5, [ + 'Drag row', + 'Select', + 'Open group', + 'Group 4', + dataWithNested[3].description, + '40', + ]); + checkRowContent(6, [ + 'Drag row', + 'Select', + undefined, + 'Sub 3-1', + dataWithNested[2].subRows[0].description, + '12', + ]); }); - it('should sort an expanded row', () => { - mount(); - cy.contains('children').click(); + it('should empty a group by dragging all items out of it', () => { + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); - cy.get('[data-testid="root-draggable-item-1"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(1), + cy.get('button[aria-roledescription="sortable"]').eq(0) + ); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(2), + cy.get('button[aria-roledescription="sortable"]').eq(0) ); - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + cy.contains('tr', 'Empty group. Drop here to add').should('exist'); - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 3) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 1 Entity a, Entity b', 'Entity 2', 'Entity 3']); - }); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(7), + cy.get('td').contains('Empty group. Drop here to add') + ); - it('should sort an expanded row by the header', () => { - mount(); - cy.contains('children').click(); - cy.get('[data-testid="created_false"]').click(); - cy.contains('children').click(); - checkRowContent(1, ['Entity 3', data[2].description, '3']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); - checkRowContent(4, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(5, ['Entity b', data[1].children![1].description, '5']); + checkRowContent(3, [ + 'Drag row', + 'Select', + 'Open group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(4, [ + 'Drag row', + 'Select', + undefined, + 'Item 2', + dataWithNested[5].description, + '60', + ]); + + cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); }); - const checkChildrenSorting = (from: string, to: string, target: { x: number; y: number }) => { - mount(); + it('should not loose selections when dragging into a dropzone', () => { + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); + }); - cy.contains('children').click(); - cy.get(from).drag(to, { - target, - force: true, + cy.contains('tr', 'Item 2').within(() => { + cy.get('input[type="checkbox"]').check(); }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(4, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); - - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 3) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 2', 'Entity 1 Entity b, Entity a', 'Entity 3']); - }; - - it('should sort children of a group from top to bottom', () => { - checkChildrenSorting( - '[data-testid="group_1-draggable-item-0"]', - '[data-testid="group_1.1"]', - { x: 5, y: 30 } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(5), + cy.get('td').contains('Empty group. Drop here to add') ); + + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="selected-subrows"]').within(() => { + cy.contains('Item 2'); + }); }); - it('should sort children of a group from bottom to top', () => { - checkChildrenSorting( - '[data-testid="group_1-draggable-item-1"]', - '[data-testid="group_1.0"]', - { x: 5, y: 0 } + it('should disable editing groups with dnd but allow sorting them internally', () => { + Nested.args.dnd = { enable: true, disableEditingGroups: true }; + mount(); + + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(5), + cy.get('td').contains('Empty group. Drop here to add') ); - }); - it('should move a parent into a group', () => { - mount(); - cy.contains('children').click(); + checkRowContent(5, ['Empty group. Drop here to add']); - cy.get('[data-testid="root-draggable-item-0"]').trigger('dragstart'); - cy.get('[data-testid="root-draggable-item-0"]').trigger('dragleave'); - cy.get('[data-testid="group_1.0"]').trigger('drop', { - target: { x: 5, y: 0 }, + cy.contains('tr', 'Group 2').within(() => { + cy.contains('button', 'Open group').realClick(); }); - cy.get('[data-testid="root-draggable-item-0"]').trigger('dragend'); - cy.contains('children').click(); - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(3, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(4, ['Entity 2', data[0].description, '2']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); - - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 2) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 1 Entity a, Entity b, Entity 2', 'Entity 3']); - }); - it('should move a child outsides a group', () => { - mount(); - cy.contains('children').click(); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(7), + cy.get('td').contains('Sub 2-1') + ); + + checkRowContent(2, ['Drag row', 'Select', 'Group', 'Group 2']); + checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-1']); + checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-2']); + checkRowContent(9, ['Drag row', 'Select', '', 'Item 2']); - cy.get('[data-testid="group_1-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(2), + cy.get('td').contains('Sub 2-2') ); - cy.get('[data-testid="group_1-draggable-item-0"]').trigger('dragend'); - cy.contains('children').click(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); - checkRowContent(4, ['Entity a', data[1].children![0].description, '4']); - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 4) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 2', 'Entity 1 Entity b', 'Entity 3', 'Entity a']); + checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-2']); + checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-1']); }); - describe('Fixed groups', () => { - it('should not move a child outsides a group if editableGroups is false', () => { - mount(); - cy.contains('children').click(); - - cy.get('[data-testid="group_1-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-1"]', - { - target: { x: 5, y: 0 }, - force: true, - } - ); - cy.get('[data-testid="group_1-draggable-item-0"]').trigger('dragend'); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(4, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); + it('should render the correct text for empty groups based on dnd status', () => { + Nested.args.dnd = { enable: false }; + Nested.args.enableSelections = false; + mount(); + + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); }); + + cy.contains('This group is empty'); }); }); }); diff --git a/app/react/V2/Components/UI/specs/TableV2.cy.tsx b/app/react/V2/Components/UI/specs/TableV2.cy.tsx deleted file mode 100644 index 9853271194..0000000000 --- a/app/react/V2/Components/UI/specs/TableV2.cy.tsx +++ /dev/null @@ -1,684 +0,0 @@ -/* eslint-disable max-statements */ -import React from 'react'; -import 'cypress-axe'; -import { mount } from '@cypress/react18'; -import { composeStories } from '@storybook/react'; -import { map } from 'lodash'; -import * as stories from 'app/stories/TableV2.stories'; -import { tableWithDisabled } from '../TableV2/specs/fixtures'; - -const { Basic, Nested, Custom } = composeStories(stories); - -describe('Table', () => { - const data = Basic.args.tableData || []; - const dataWithNested = Nested.args.tableData || []; - - const checkRowContent = (rowNumber: number, cellsContent: string[]) => { - cellsContent.forEach( - (content, index) => - content && - cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content) - ); - }; - - it('should be accessible', () => { - cy.injectAxe(); - mount(); - cy.checkA11y(); - }); - - beforeEach(() => { - Basic.args.defaultSorting = undefined; - Basic.args.enableSelections = false; - Basic.args.dnd = { enable: false, disableEditingGroups: false }; - }); - - it('Should return a table with the columns and row specified', () => { - mount(); - const toStrings = (cells: JQuery) => map(cells, 'textContent'); - cy.get('tr th').then(toStrings).should('eql', ['Title', 'Description', 'Date added']); - - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 4', data[2].description, '4']); - checkRowContent(4, ['Entity 3', data[3].description, '3']); - checkRowContent(5, ['Entity 5', data[4].description, '5']); - }); - - it('should render the data in a custom component and styles', () => { - mount(); - cy.get('table').within(() => { - cy.contains('th', 'Description').should( - 'have.attr', - 'class', - 'p-4 text-sm text-gray-500 uppercase border-b bg-blue-700 text-white' - ); - cy.contains('td', 'Entity 2').should( - 'have.attr', - 'class', - 'relative px-4 py-2 bg-gray-100 text-red-700 ' - ); - cy.contains('td', 'Entity 1').should( - 'have.attr', - 'class', - 'relative px-4 py-2 bg-gray-100 text-red-700 ' - ); - cy.get('div[class="text-white bg-orange-500"]').should('have.length', 5); - cy.get('button').should('have.length', 5); - }); - }); - - it('should trigger the custom action for the buttons', () => { - Custom.args.actionFn = cy.spy().as('actionSpy'); - mount(); - - cy.contains('tr', 'Entity 1').within(() => { - cy.contains('button', 'Action').realClick(); - }); - - cy.get('@actionSpy').should('have.been.calledOnceWith', 'A1'); - }); - - describe('Sorting', () => { - it('Should be sortable by title', () => { - mount(); - - cy.get('th').contains('Title').realClick(); - cy.contains('button', 'Save changes').realClick(); - - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[3].description, '3']); - checkRowContent(4, ['Entity 4', data[2].description, '4']); - checkRowContent(5, ['Entity 5', data[4].description, '5']); - }); - - it('should return to the default sorting', () => { - mount(); - cy.get('th').contains('Title').realClick().realClick().realClick(); - - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 4', data[2].description, '4']); - checkRowContent(4, ['Entity 3', data[3].description, '3']); - checkRowContent(5, ['Entity 5', data[4].description, '5']); - }); - - it('should keep selections when sorting', () => { - Basic.args.enableSelections = true; - mount(); - - cy.get('tbody').within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); - - cy.get('th').contains('Title').realClick().realClick(); - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 2'); - cy.contains('Entity 4'); - }); - }); - - it('should sort items in groups', () => { - mount(); - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - checkRowContent(1, [ - 'Drag row', - 'Select', - 'Group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(2, [ - 'Drag row', - 'Select', - '', - 'Sub 1-1', - dataWithNested[0].subRows[0].description, - '5', - ]); - checkRowContent(3, [ - 'Drag row', - 'Select', - '', - 'Sub 1-2', - dataWithNested[0].subRows[1].description, - '7', - ]); - - cy.get('th').contains('Title').realClick().realClick(); - - checkRowContent(6, [ - 'Drag row', - 'Select', - 'Group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(7, [ - 'Drag row', - 'Select', - '', - 'Sub 1-2', - dataWithNested[0].subRows[1].description, - '7', - ]); - checkRowContent(8, [ - 'Drag row', - 'Select', - '', - 'Sub 1-1', - dataWithNested[0].subRows[0].description, - '5', - ]); - }); - - it('should disable sorting when defined in the columns', () => { - mount(); - cy.get('tr th').contains('Title').children().should('have.length', 1); - cy.get('tr th').contains('Description').children().should('have.length', 0); - cy.get('tr th').contains('Date added').children().should('have.length', 1); - - cy.get('th').contains('Description').realClick(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - }); - - it('Should sort the rows with the sorting state specified', () => { - Basic.args.defaultSorting = [{ id: 'created', desc: false }]; - mount(); - - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[3].description, '3']); - }); - - it('should reset sorting state when using dnd after sorting', () => { - Basic.args.dnd = { enable: true }; - mount(); - - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.get('th').contains('Title').realClick().realClick(); - cy.contains('Sorted by title'); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(6), - cy.get('button[aria-roledescription="sortable"]').eq(2) - ); - - cy.contains('No sorting'); - }); - - it('it should sort items within groups', () => { - Nested.args.dnd = { enable: false }; - Nested.args.enableSelections = false; - mount(); - - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - checkRowContent(1, ['Open group', 'Group 1', dataWithNested[0].description, '10']); - checkRowContent(2, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); - checkRowContent(3, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); - - cy.contains('th', 'Title').realClick().realClick(); - - checkRowContent(6, ['Open group', 'Group 1', dataWithNested[0].description, '10']); - checkRowContent(7, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); - checkRowContent(8, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); - }); - - it('should allow manually controlling the sorting', () => { - const setSortingSpy = cy.stub().as('setSortingSpy'); - Basic.args.sortingFn = setSortingSpy; - - mount(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - cy.get('th').contains('Title').realClick(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - - cy.get('@setSortingSpy').should('have.been.calledTwice'); - cy.get('@setSortingSpy').should('have.been.calledWith', []); - cy.get('@setSortingSpy').should('have.been.calledWith', [{ id: 'title', desc: false }]); - }); - }); - - describe('Selections', () => { - beforeEach(() => { - Basic.args.enableSelections = true; - Basic.args.dnd = { enable: true }; - mount(); - }); - - it('should select and unselect some items selections', () => { - cy.contains('Select all').realClick(); - cy.get('tbody').within(() => { - cy.get('input[type="checkbox"]').eq(0).uncheck(); - cy.get('input[type="checkbox"]').eq(2).uncheck(); - }); - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 1'); - cy.contains('Entity 2').should('not.exist'); - cy.contains('Entity 3'); - cy.contains('Entity 4').should('not.exist'); - cy.contains('Entity 5'); - }); - }); - - it('should reset selections when adding a new entry to the table', () => { - cy.contains('Select all').realClick(); - cy.contains('button', 'Save changes').realClick(); - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 1'); - cy.contains('Entity 2'); - cy.contains('Entity 3'); - cy.contains('Entity 4'); - cy.contains('Entity 5'); - }); - cy.get('#checkbox-header').should('be.checked'); - - cy.contains('button', 'Add new item').realClick(); - cy.get('#checkbox-header').should('not.be.checked'); - }); - - it('should reset selections when removing an item from the table', () => { - cy.contains('Select all').realClick(); - cy.contains('button', 'Save changes').realClick(); - cy.get('#checkbox-header').should('be.checked'); - cy.contains('button', 'Remove last item').realClick(); - cy.get('#checkbox-header').should('not.be.checked'); - }); - - it('should not select items with disabled row selection', () => { - Nested.args.enableSelections = true; - Nested.args.dnd = { enable: true }; - Nested.args.tableData = tableWithDisabled; - mount(); - cy.contains('Select all').realClick(); - cy.contains('button', 'Save changes').realClick(); - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Group 1'); - cy.contains('Group 2'); - cy.contains('Item 1').should('not.exist'); - cy.contains('Item 2'); - }); - cy.get('[data-testid="selected-subrows"]').within(() => { - cy.contains('Sub 1-1').should('not.exist'); - cy.contains('Sub 1-2'); - cy.contains('Sub 1-3'); - }); - }); - - it('should change parent status based on selected children', () => { - Nested.args.enableSelections = true; - Nested.args.dnd = { enable: true }; - Nested.args.tableData = tableWithDisabled; - mount(); - - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - cy.get('input').realClick().should('be.checked'); - }); - cy.contains('tr', 'Sub 1-1').within(() => { - cy.get('input').should('not.be.checked'); - }); - cy.contains('tr', 'Sub 1-2').within(() => { - cy.get('input').should('be.checked'); - cy.get('input').realClick(); - }); - cy.contains('tr', 'Sub 1-3').within(() => { - cy.get('input').should('be.checked'); - cy.get('input').realClick(); - }); - cy.contains('tr', 'Group 1').within(() => { - cy.get('input').should('be.checked'); - }); - }); - }); - - describe('DnD', () => { - beforeEach(() => { - Basic.args.dnd = { enable: true }; - Basic.args.enableSelections = true; - Nested.args.tableData = dataWithNested; - mount(); - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Entity 2 Entity 1 Entity 4 Entity 3 Entity 5'); - }); - }); - - it('should sort rows by dragging', () => { - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(1), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(3), - cy.get('button[aria-roledescription="sortable"]').eq(2) - ); - - checkRowContent(1, ['Drag row', 'Select', 'Entity 1', data[1].description, '1']); - checkRowContent(2, ['Drag row', 'Select', 'Entity 2', data[0].description, '2']); - checkRowContent(3, ['Drag row', 'Select', 'Entity 3', data[3].description, '3']); - checkRowContent(4, ['Drag row', 'Select', 'Entity 4', data[2].description, '4']); - checkRowContent(5, ['Drag row', 'Select', 'Entity 5', data[4].description, '5']); - - cy.contains('button', 'Save changes').realClick(); - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Entity 1 Entity 2 Entity 3 Entity 4 Entity 5'); - }); - }); - - it('should keep selections while dragging', () => { - cy.get('tbody').within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(1), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 1').should('not.exist'); - cy.contains('Entity 2'); - cy.contains('Entity 4'); - cy.contains('Entity 3').should('not.exist'); - cy.contains('Entity 5').should('not.exist'); - }); - - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Entity 1 Entity 2 Entity 4 Entity 3 Entity 5'); - }); - }); - }); - - describe('Nested data', () => { - beforeEach(() => { - Nested.args.dnd = { enable: true }; - Nested.args.enableSelections = true; - mount(); - }); - - it('should check the content', () => { - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Group 1 Group 2 Group 3 Group 4 Item 1 Item 2'); - }); - checkRowContent(1, [ - 'Drag row', - 'Select', - 'Group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(2, [ - 'Drag row', - 'Select', - 'Group', - 'Group 2', - dataWithNested[1].description, - '20', - ]); - checkRowContent(3, [ - 'Drag row', - 'Select', - 'Group', - 'Group 3', - dataWithNested[2].description, - '30', - ]); - checkRowContent(4, [ - 'Drag row', - 'Select', - 'Group', - 'Group 4', - dataWithNested[3].description, - '40', - ]); - checkRowContent(5, [ - 'Drag row', - 'Select', - undefined, - 'Item 1', - dataWithNested[4].description, - '50', - ]); - checkRowContent(6, [ - 'Drag row', - 'Select', - undefined, - 'Item 2', - dataWithNested[5].description, - '60', - ]); - }); - - it('should expand groups and check for accessibility', () => { - cy.get('tbody').within(() => { - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('tr', 'Group 2').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('tr', 'Group 3').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('td', 'Sub 1-1'); - cy.contains('td', 'Sub 1-2'); - cy.contains('td', 'Sub 2-1'); - cy.contains('td', 'Sub 2-2'); - cy.contains('td', 'Sub 3-1'); - cy.contains('td', 'Sub 1-2'); - }); - - cy.checkA11y(); - }); - - it('should sort children element with dnd', () => { - cy.get('tbody').within(() => { - cy.contains('tr', 'Group 1').within(() => { - cy.contains('Open group').realClick(); - }); - cy.contains('tr', 'Group 3').within(() => { - cy.contains('Open group').realClick(); - }); - cy.contains('td', 'Sub 1-1'); - cy.contains('td', 'Sub 1-2'); - cy.contains('td', 'Sub 3-1'); - cy.contains('td', 'Sub 3-2'); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(5), - cy.get('button[aria-roledescription="sortable"]').eq(1) - ); - - checkRowContent(1, [ - 'Drag row', - 'Select', - 'Open group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(2, [ - 'Drag row', - 'Select', - undefined, - 'Sub 3-1', - dataWithNested[2].subRows[0].description, - '12', - ]); - - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="sorted-subrows"] > .flex > :nth-child(1)').contains( - '|Group 1 - Sub 3-1|' - ); - }); - - it('should add an item to an empty group', () => { - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('tr', 'Group 3').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(3), - cy.get('td').contains('Empty group. Drop here to add') - ); - - checkRowContent(5, [ - 'Drag row', - 'Select', - 'Open group', - 'Group 4', - dataWithNested[3].description, - '40', - ]); - checkRowContent(6, [ - 'Drag row', - 'Select', - undefined, - 'Sub 3-1', - dataWithNested[2].subRows[0].description, - '12', - ]); - }); - - it('should empty a group by dragging all items out of it', () => { - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(1), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(2), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - - cy.contains('tr', 'Empty group. Drop here to add').should('exist'); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(7), - cy.get('td').contains('Empty group. Drop here to add') - ); - - checkRowContent(3, [ - 'Drag row', - 'Select', - 'Open group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(4, [ - 'Drag row', - 'Select', - undefined, - 'Item 2', - dataWithNested[5].description, - '60', - ]); - - cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); - }); - - it('should not loose selections when dragging into a dropzone', () => { - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.contains('tr', 'Item 2').within(() => { - cy.get('input[type="checkbox"]').check(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(5), - cy.get('td').contains('Empty group. Drop here to add') - ); - - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-subrows"]').within(() => { - cy.contains('Item 2'); - }); - }); - - it('should disable editing groups with dnd but allow sorting them internally', () => { - Nested.args.dnd = { enable: true, disableEditingGroups: true }; - mount(); - - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(5), - cy.get('td').contains('Empty group. Drop here to add') - ); - - checkRowContent(5, ['Empty group. Drop here to add']); - - cy.contains('tr', 'Group 2').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(7), - cy.get('td').contains('Sub 2-1') - ); - - checkRowContent(2, ['Drag row', 'Select', 'Group', 'Group 2']); - checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-1']); - checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-2']); - checkRowContent(9, ['Drag row', 'Select', '', 'Item 2']); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(2), - cy.get('td').contains('Sub 2-2') - ); - - checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-2']); - checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-1']); - }); - - it('should render the correct text for empty groups based on dnd status', () => { - Nested.args.dnd = { enable: false }; - Nested.args.enableSelections = false; - mount(); - - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.contains('This group is empty'); - }); - }); -}); diff --git a/app/react/V2/Components/componentWrappers.tsx b/app/react/V2/Components/componentWrappers.tsx deleted file mode 100644 index fe17e7eaf4..0000000000 --- a/app/react/V2/Components/componentWrappers.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; - -/* eslint-disable comma-spacing */ -const withLazy = - (Component: React.FC, moduleImport: Function, extractor: (module: unknown) => {}) => - (props: T & { key?: string }) => { - const lazyModuleRef = useRef({}); - const [isLoaded, setIsLoaded] = useState(false); - - useEffect(() => { - moduleImport().then((module: unknown) => { - lazyModuleRef.current = extractor(module); - setIsLoaded(true); - return module; - }); - }, []); - - const componentProps = { ...lazyModuleRef.current, ...props }; - - return isLoaded ? : null; - }; - -/* eslint-disable comma-spacing */ -const withDnD = (Component: React.FC) => - withLazy( - Component, - async () => import('react-dnd'), - (module: any) => ({ - useDrag: module.useDrag, - useDrop: module.useDrop, - useDragDropManager: module.useDragDropManager, - DndProvider: module.DndProvider, - }) - ); - -/* eslint-disable comma-spacing */ -const withDnDBackend = (Component: React.FC) => - withLazy( - Component, - async () => import('react-dnd-html5-backend'), - (module: any) => ({ - HTML5Backend: module.HTML5Backend, - }) - ); -export { withDnD, withDnDBackend }; diff --git a/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx b/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx index c69f7d20a8..e8c5f43174 100644 --- a/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx +++ b/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx @@ -7,13 +7,7 @@ import { useAtomValue } from 'jotai'; import { Translate } from 'app/I18N'; import { ClientSettings } from 'app/apiResponseTypes'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; -import { - Button, - PaginationState, - Paginator, - Pill, - Table_deprecated as Table, -} from 'app/V2/Components/UI'; +import { Button, PaginationState, Paginator, Pill, Table } from 'app/V2/Components/UI'; import { useIsFirstRender } from 'app/V2/CustomHooks/useIsFirstRender'; import { settingsAtom } from 'app/V2/atoms'; import { ActivityLogEntryType } from 'shared/types/activityLogEntryType'; @@ -98,12 +92,18 @@ const ActivityLog = () => { {error === undefined && ( - - title={Activity Log} - columns={columns} + { + setSorting(sortingState); + }} + header={ + + Activity Log + + } footer={
({ ...row, rowId: row._id })), totalPages, page: params.page, total: activityLogList.totalRows, @@ -153,6 +155,7 @@ const filterPairs = (filters: ActivityLogSearch) => { }); return _(plainFilters).sortBy(0).value(); }; + const updateSearch = ( filters: ActivityLogSearch, searchParams: URLSearchParams, @@ -197,7 +200,7 @@ const buildPageURL = (appliedFilters: any, pageTo: string | number, location: Lo return `${location.pathname}?${createSearchParams(newParams)}`; }; -export type { LoaderData, ActivityLogSearch }; +export type { LoaderData, ActivityLogSearch, LogEntry }; export { activityLogLoader, getAppliedFilters, diff --git a/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx b/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx index 2186ae0299..a02e96be5a 100644 --- a/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx +++ b/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx @@ -6,14 +6,15 @@ import { Tooltip } from 'flowbite-react'; import { Pill, Button } from 'app/V2/Components/UI'; import type { PillColor } from 'app/V2/Components/UI'; import { Translate } from 'app/I18N'; -import { ActivityLogEntryType, ActivityLogSemanticType } from 'shared/types/activityLogEntryType'; +import { ActivityLogSemanticType } from 'shared/types/activityLogEntryType'; +import { LogEntry } from '../ActivityLogLoader'; const ActionHeader = () => Action; const UserHeader = () => User; const DescriptionHeader = () => Description; const TimeHeader = () => Timestamp; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); type Methods = 'CREATE' | 'UPDATE' | 'DELETE' | 'RAW' | 'MIGRATE' | 'WARNING'; const methodColors: Map = new Map([ @@ -33,15 +34,15 @@ const ActionPill = ({ action, className = '' }: { action: string; className?: st ); }; -const ActionCell = ({ cell }: CellContext) => ( +const ActionCell = ({ cell }: CellContext) => ( ); -const UserCell = ({ cell }: CellContext) => ( +const UserCell = ({ cell }: CellContext) => ( {cell.getValue()} ); -const DescriptionCell = ({ cell }: CellContext) => { +const DescriptionCell = ({ cell }: CellContext) => { const semanticData = cell.getValue(); return ( @@ -49,16 +50,16 @@ const DescriptionCell = ({ cell }: CellContext -
+
Query {cell.row.original.query}
-
+
Body - + {cell.row.original.body}
@@ -89,7 +90,7 @@ const DescriptionCell = ({ cell }: CellContext - ({ cell }: CellContext) => { + ({ cell }: CellContext) => { const date = moment(cell.getValue()); return ( <> @@ -99,7 +100,7 @@ const TimeCell = ); }; -const ViewCell = ({ cell, column }: CellContext) => ( +const ViewCell = ({ cell, column }: CellContext) => (
{ - setShowSipanel(true); + setShowSidepanel(true); setFileToEdit(file); })} - data={files} - title={Custom Uploads} + onChange={({ selectedRows: selected }) => { + setSelectedRows(files.filter(file => file.rowId in selected)); + }} + enableSelections + header={ + + Custom Uploads + + } /> @@ -177,11 +182,12 @@ const CustomUploads = () => { setShowSipanel(false)} + closeSidepanel={() => setShowSidepanel(false)} file={fileToEdit} /> ); }; +export type { CustomUpload }; export { CustomUploads, customUploadsLoader }; diff --git a/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx b/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx index c6475a5624..261ee75166 100644 --- a/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx +++ b/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx @@ -3,19 +3,20 @@ import React from 'react'; import { useForm } from 'react-hook-form'; import { useRevalidator } from 'react-router-dom'; import { useSetAtom } from 'jotai'; -import { Translate } from 'app/I18N'; import { FileType } from 'shared/types/fileType'; +import { Translate } from 'app/I18N'; import { FetchResponseError } from 'shared/JSONRequest'; import { Button, Card, Sidepanel } from 'V2/Components/UI'; import { InputField } from 'V2/Components/Forms'; import { getFileNameAndExtension } from 'V2/shared/formatHelpers'; import { notificationAtom } from 'V2/atoms'; import { update } from 'V2/api/files'; +import { CustomUpload } from '../CustomUploads'; type EditFileSidepanelProps = { showSidepanel: boolean; closeSidepanel: () => any; - file?: FileType; + file?: CustomUpload; }; const EditFileSidepanel = ({ showSidepanel, closeSidepanel, file }: EditFileSidepanelProps) => { @@ -52,7 +53,8 @@ const EditFileSidepanel = ({ showSidepanel, closeSidepanel, file }: EditFileSide }); const save = async (data: { filename: string } | { filename: undefined }) => { - const updatedFile: FileType = { ...file, originalname: `${data.filename}.${extension}` }; + const updatedFile = { ...file, originalname: `${data.filename}.${extension}` }; + delete updatedFile.rowId; const response = await update(updatedFile); closeSidepanel(); notify(response); diff --git a/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx b/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx index d8b34ee11a..0ad1f6374b 100644 --- a/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx +++ b/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx @@ -1,20 +1,20 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { FileType } from 'shared/types/fileType'; import { Translate } from 'app/I18N'; import { Button, FileIcon } from 'V2/Components/UI'; +import { CustomUpload } from '../CustomUploads'; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const TitleHeader = () => Name; const PreviewHeader = () => Preview; const URLHeader = () => URL; -const ActionHeader = () => Action; +const ActionHeader = () => Action; -const TitleCell = ({ getValue }: CellContext) => getValue(); -const URLCell = ({ getValue }: CellContext) => `/assets/${getValue()}`; -const PreviewCell = ({ cell }: CellContext) => { +const TitleCell = ({ getValue }: CellContext) => getValue(); +const URLCell = ({ getValue }: CellContext) => `/assets/${getValue()}`; +const PreviewCell = ({ cell }: CellContext) => { const { mimetype = '', originalname, filename } = cell.row.original; return (
@@ -27,7 +27,8 @@ const PreviewCell = ({ cell }: CellContext) => {
); }; -const ActionCell = ({ cell }: CellContext) => { + +const ActionCell = ({ cell }: CellContext) => { const actions = cell.column.columnDef.meta?.action ? cell.column.columnDef.meta?.action() : undefined; @@ -55,8 +56,8 @@ const ActionCell = ({ cell }: CellContext) => { }; const createColumns = ( - handleDelete: (file: FileType) => void, - editFile: (file: FileType) => void + handleDelete: (file: CustomUpload) => void, + editFile: (file: CustomUpload) => void ) => [ columnHelper.display({ id: 'preview', @@ -82,10 +83,7 @@ const createColumns = ( header: ActionHeader, cell: ActionCell, enableSorting: false, - meta: { - action: () => ({ delete: handleDelete, edit: editFile }), - headerClassName: 'w-0 sr-only', - }, + meta: { action: () => ({ delete: handleDelete, edit: editFile }) }, }), ]; diff --git a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx index 5163c77ace..59d55131ca 100644 --- a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx +++ b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx @@ -1,18 +1,17 @@ /* eslint-disable max-statements */ -import React, { useEffect, useMemo, useState } from 'react'; -import { LoaderFunction, useBlocker, useLoaderData } from 'react-router-dom'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { LoaderFunction, useBlocker, useLoaderData, useRevalidator } from 'react-router-dom'; import { useSetAtom } from 'jotai'; -import { Row } from '@tanstack/react-table'; import { IncomingHttpHeaders } from 'http'; +import { RowSelectionState } from '@tanstack/react-table'; import { CheckCircleIcon } from '@heroicons/react/24/outline'; import { FetchResponseError } from 'shared/JSONRequest'; -import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; import { Translate } from 'app/I18N'; import { notificationAtom, settingsAtom } from 'V2/atoms'; import * as settingsAPI from 'V2/api/settings'; import * as templatesAPI from 'V2/api/templates'; import { SettingsContent } from 'V2/Components/Layouts/SettingsContent'; -import { Button, Table_deprecated as Table } from 'V2/Components/UI'; +import { Button, Table } from 'V2/Components/UI'; import { ConfirmNavigationModal } from 'V2/Components/Forms'; import { createColumns, @@ -25,6 +24,8 @@ import { sidepanelAtom, LoaderData, sanitizeFilters, + formatFilters, + Filter, } from './components'; const filtersLoader = @@ -32,36 +33,36 @@ const filtersLoader = async () => { const { filters } = await settingsAPI.get(headers); const templates = await templatesAPI.get(headers); - - return { filters, templates }; + const tableFilters: LoaderData['filters'] = formatFilters(filters || []); + return { filters: tableFilters, templates }; }; const FiltersTable = () => { - const loaderData = useLoaderData() as LoaderData; + const { filters: loadedFilters = [], templates: loadedTemplates } = useLoaderData() as LoaderData; + const currentFilters = useRef(loadedFilters); const [hasChanges, setHasChanges] = useState(false); const [disabled, setDisabled] = useState(false); const [showModal, setShowModal] = useState(false); const [confirmNavigationModal, setConfirmNavigationModal] = useState(false); const [showSidepanel, setShowSidepanel] = useState(false); - const [filters, setFilters] = useState(loaderData.filters); - const [selectedFilters, setSelectedFilters] = useState[]>([]); + const [filters, setFilters] = useState(loadedFilters); + const [selectedFilters, setSelectedFilters] = useState({}); const blocker = useBlocker(hasChanges); const setAtom = useSetAtom(sidepanelAtom); const setNotifications = useSetAtom(notificationAtom); const setSettings = useSetAtom(settingsAtom); + const revalidator = useRevalidator(); const templates = useMemo( - () => filterAvailableTemplates(loaderData.templates, filters), - [filters, loaderData.templates] + () => filterAvailableTemplates(loadedTemplates, filters), + [filters, loadedTemplates] ); useEffect(() => { - if (JSON.stringify(filters) !== JSON.stringify(loaderData.filters)) { - setHasChanges(true); - } else { - setHasChanges(false); - } - }, [filters, loaderData.filters]); + const formattedFilters = formatFilters(loadedFilters || []); + currentFilters.current = formattedFilters; + setFilters(formattedFilters); + }, [loadedFilters]); useEffect(() => { if (blocker.state === 'blocked') { @@ -70,24 +71,39 @@ const FiltersTable = () => { }, [blocker, setConfirmNavigationModal]); const cancel = () => { - setFilters(loaderData.filters); + currentFilters.current = loadedFilters; + setFilters(loadedFilters); + setHasChanges(false); }; const addNewFilters = (templatedIds: string[]) => { const newFilters = createNewFilters(templatedIds, templates); - setFilters([...(filters || []), ...newFilters]); + setFilters([...currentFilters.current, ...newFilters]); }; const handleDelete = () => { - const idsToRemove = selectedFilters.map(selected => selected.original.id); - const updatedFilters = deleteFilters(filters, idsToRemove); - setFilters(updatedFilters); + const idsToRemove: string[] = []; + currentFilters.current?.forEach(filter => { + if (filter.rowId in selectedFilters) { + idsToRemove.push(filter.rowId); + } + if (filter.subRows) { + filter.subRows.forEach(subRow => { + if (subRow.rowId in selectedFilters) { + idsToRemove.push(subRow.rowId); + } + }); + } + }); + + const updatedFilters = deleteFilters(currentFilters.current, idsToRemove); + setFilters(updatedFilters || []); }; const handleSave = async () => { setDisabled(true); - const response = await settingsAPI.save({ filters: sanitizeFilters(filters) }); - + const filtersToSave = sanitizeFilters(currentFilters.current); + const response = await settingsAPI.save({ filters: filtersToSave }); if (response instanceof FetchResponseError) { return setNotifications({ type: 'error', @@ -95,18 +111,33 @@ const FiltersTable = () => { ...(response.message && { details: response.message }), }); } - setSettings(response); setDisabled(false); setHasChanges(false); + revalidator.revalidate(); return setNotifications({ type: 'success', text: Filters saved }); }; + const handleChange = ({ + rows, + selectedRows, + }: { + rows: Filter[]; + selectedRows: RowSelectionState; + }) => { + currentFilters.current = rows; + setSelectedFilters(selectedRows); + if (JSON.stringify(currentFilters.current) !== JSON.stringify(loadedFilters)) { + setHasChanges(true); + } else { + setHasChanges(false); + } + }; + return (
-
@@ -136,25 +167,21 @@ const FiltersTable = () => {
- - - draggableRows - enableSelection - subRowsKey="items" - onChange={updatedFilters => { - setFilters(updatedFilters); - }} - onSelection={selected => { - setSelectedFilters(selected); - }} +
Filters} + data={filters} + header={ + + Filters + + } /> - - {selectedFilters.length ? ( + {Object.keys(selectedFilters).length ? ( @@ -175,16 +202,7 @@ const FiltersTable = () => { Add group -
- +
)}
- {showModal && ( { onAdd={templateIds => addNewFilters(templateIds)} /> )} - {confirmNavigationModal && ( { }} /> )} - { if (newFilter) { - setFilters(updateFilters(newFilter, filters)); + setFilters(updateFilters(newFilter, filters) || []); } }} availableTemplates={templates} diff --git a/app/react/V2/Routes/Settings/Filters/components/FiltersSidepanel.tsx b/app/react/V2/Routes/Settings/Filters/components/FiltersSidepanel.tsx index 9cf0e4dbe2..3421cd5aef 100644 --- a/app/react/V2/Routes/Settings/Filters/components/FiltersSidepanel.tsx +++ b/app/react/V2/Routes/Settings/Filters/components/FiltersSidepanel.tsx @@ -6,16 +6,15 @@ import { useLoaderData } from 'react-router-dom'; import uniqueID from 'shared/uniqueID'; import { Translate } from 'app/I18N'; import { ClientTemplateSchema } from 'app/istore'; -import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; import { Button, Card, Sidepanel } from 'V2/Components/UI'; import { InputField, MultiSelect } from 'V2/Components/Forms'; import { sidepanelAtom } from './sidepanelAtom'; -import { LoaderData } from './helpers'; +import { Filter, LoaderData } from './helpers'; type FiltersSidepanelProps = { showSidepanel: boolean; setShowSidepanel: React.Dispatch>; - onSave: (newFilter: ClientSettingsFilterSchema | undefined) => void; + onSave: (newFilter: Filter | undefined) => void; availableTemplates?: ClientTemplateSchema[]; }; @@ -27,7 +26,7 @@ const FiltersSidepanel = ({ }: FiltersSidepanelProps) => { const { templates: allTemplates } = useLoaderData() as LoaderData; const filter = useAtomValue(sidepanelAtom); - const multiselectValues = filter?.items?.map(item => item.id).filter(v => v) as + const multiselectValues = filter?.subRows?.map(item => item.id).filter(v => v) as | string[] | undefined; @@ -47,11 +46,12 @@ const FiltersSidepanel = ({ value: availableTemplate._id!, })); - const defaultValues = { ...filter, items: selectedValues }; + const defaultValues = { ...filter, subRows: selectedValues, rowId: filter?.rowId || 'NEW' }; const { register, handleSubmit, control, + reset, formState: { errors }, } = useForm({ defaultValues, @@ -60,19 +60,21 @@ const FiltersSidepanel = ({ const closeSidepanel = () => { setShowSidepanel(false); + reset(); }; const formatSelected = (selected: string[] | undefined) => selected?.map(selection => { const templateName = allTemplates?.find(template => template._id === selection)?.name; - return { id: selection, name: templateName }; + return { id: selection, name: templateName, rowId: selection }; }); - const handleSave = (values: Omit & { items: string[] }) => { - const result = { ...values, items: formatSelected(values.items) }; + const handleSave = (values: Omit & { subRows: string[] }) => { + const result = { ...values, subRows: formatSelected(values.subRows) }; if (!filter?._id) delete result._id; if (!filter?.id) result.id = uniqueID(); + if (result.rowId === defaultValues.rowId) result.rowId = uniqueID(); onSave(result); closeSidepanel(); @@ -89,6 +91,7 @@ const FiltersSidepanel = ({ + General Information} className="mb-4"> ( Entity types} @@ -118,9 +120,6 @@ const FiltersSidepanel = ({ /> )} /> - {errors.items?.type === 'required' && ( - This field is required - )} diff --git a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx index 092d038559..1b29cab01b 100644 --- a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx @@ -1,62 +1,44 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; -import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; import { useSetAtom } from 'jotai'; import { Translate } from 'app/I18N'; -import { Button, EmbededButton } from 'V2/Components/UI'; +import { Button } from 'V2/Components/UI'; import { sidepanelAtom } from './sidepanelAtom'; +import { Filter } from './helpers'; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const TitleHeader = () => Label; -const ActionHeader = () => Action; +const ActionHeader = () => Action; -const Filters = ({ row, getValue }: CellContext) => ( -
- - {getValue()} - - {row.getCanExpand() && ( - : } - onClick={() => row.toggleExpanded()} - color="indigo" - className="bg-indigo-200 rounded-md border-none drop-shadow-none" - > - Group - - )} -
+const Filters = ({ getValue }: CellContext) => ( +
{getValue()}
); -const ActionCell = ({ cell, row }: CellContext) => { +const ActionCell = ({ cell, row }: CellContext) => { const action = cell.column.columnDef.meta?.action; const setAtom = useSetAtom(sidepanelAtom); - if (!cell.getIsAggregated()) { - return undefined; + if (row.originalSubRows) { + return ( + + ); } - return ( - - ); + return undefined; }; const createColumns = (setSidepanel: React.Dispatch>) => [ @@ -71,10 +53,7 @@ const createColumns = (setSidepanel: React.Dispatch { +const filterAvailableTemplates = (templates: ClientTemplateSchema[], filters?: Filter[]) => { const usedTemplatesIds: string[] = []; filters?.forEach(filter => { - if (filter.items) { - filter.items.forEach(item => { + if (filter.subRows) { + filter.subRows.forEach(item => { usedTemplatesIds.push(item.id!); }); } @@ -30,19 +37,16 @@ const filterAvailableTemplates = ( const createNewFilters = ( selectedTemplatesIds: string[], templates?: ClientTemplateSchema[] -): ClientSettingsFilterSchema[] => { +): Filter[] => { const newFilters = selectedTemplatesIds.map(templateId => { const template = templates?.find(templ => templ._id === templateId); - return { id: templateId, name: template?.name }; + return { id: templateId, name: template?.name, rowId: templateId }; }); return newFilters; }; -const updateFilters = ( - newFilter: ClientSettingsFilterSchema, - filters?: ClientSettingsFilterSchema[] -) => { +const updateFilters = (newFilter: Filter, filters?: Filter[]) => { let isNewFilter = true; const updatedFilters = filters?.map(filter => { @@ -60,65 +64,68 @@ const updateFilters = ( return updatedFilters; }; -const deleteFilters = ( - originalFilters?: ClientSettingsFilterSchema[], - filtersToRemove?: (string | undefined)[] -) => { +const deleteFilters = (originalFilters?: Filter[], filtersToRemove?: (string | undefined)[]) => { if (!filtersToRemove) { return originalFilters; } - return originalFilters - ?.map(filter => { - if (filtersToRemove.includes(filter.id!)) { - return {}; - } + const updatedFilters: Filter[] = []; - if (filter.items) { - const nestedFilters = filter.items.filter(item => !filtersToRemove.includes(item.id!)); - return { ...filter, items: nestedFilters }; + originalFilters?.forEach(filter => { + const updatedFilter = { ...filter }; + if (!filtersToRemove.includes(filter.rowId)) { + if (filter.subRows) { + const subRows = filter.subRows.filter(item => !filtersToRemove.includes(item.rowId)); + updatedFilter.subRows = subRows; } + updatedFilters.push(updatedFilter); + } + }); - return { ...filter }; - }) - .filter(filter => { - if (!filter.id) { - return false; - } - if (filter.items && filter.items.length === 0) { - return false; - } - return true; - }); + return updatedFilters; }; -const sanitizeFilters = (filters?: ClientSettingsFilterSchema[]) => { - const sanitizedFilters = filters?.map(filter => { - const sanitizedFilter = { ...filter }; +const sanitizeFilters = (filters?: Filter[]) => { + const sanitizedFilters: ClientSettingsFilterSchema[] = []; - if (filter.items) { - sanitizedFilter.items = filter.items.map( - (item: { id?: string; label?: string; _id?: string }) => { - const sanitizedItem = { ...item }; - if (sanitizedItem._id) { - delete sanitizedItem._id; - } - return sanitizedItem; - } + filters?.forEach(filter => { + const { rowId, subRows, ...sanitizedFilter } = { ...filter }; + + if (subRows && subRows.length === 0) { + return; + } + + if (subRows) { + sanitizedFilter.items = subRows.map( + ({ rowId: itemRowId, _id, ...sanitizedItem }) => sanitizedItem ); } - return sanitizedFilter; + sanitizedFilters.push(sanitizedFilter); }); return sanitizedFilters; }; -export type { LoaderData }; +const formatFilters = (filters: ClientSettingsFilterSchema[]): Filter[] => + filters?.map(filter => { + const tableFilter: Filter = { + ...filter, + rowId: filter._id!, + }; + if (filter.items) { + const subRows = filter.items.map(item => ({ ...item, rowId: item.id! })); + tableFilter.subRows = subRows; + } + return tableFilter; + }); + +export type { LoaderData, Filter }; export { filterAvailableTemplates, createNewFilters, updateFilters, deleteFilters, sanitizeFilters, + formatFilters, }; diff --git a/app/react/V2/Routes/Settings/Filters/components/index.ts b/app/react/V2/Routes/Settings/Filters/components/index.ts index 02f0cbae75..488fc51946 100644 --- a/app/react/V2/Routes/Settings/Filters/components/index.ts +++ b/app/react/V2/Routes/Settings/Filters/components/index.ts @@ -7,6 +7,7 @@ export { createNewFilters, deleteFilters, sanitizeFilters, + formatFilters, } from './helpers'; export { sidepanelAtom } from './sidepanelAtom'; -export type { LoaderData } from './helpers'; +export type { LoaderData, Filter } from './helpers'; diff --git a/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts b/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts index e1a2e8539a..4893768b2f 100644 --- a/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts +++ b/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts @@ -1,6 +1,6 @@ import { atom } from 'jotai'; -import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; +import { Filter } from './helpers'; -const sidepanelAtom = atom({} as ClientSettingsFilterSchema | undefined); +const sidepanelAtom = atom({} as Filter | undefined); export { sidepanelAtom }; diff --git a/app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts b/app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts new file mode 100644 index 0000000000..a1326f5f50 --- /dev/null +++ b/app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts @@ -0,0 +1,72 @@ +import { Filter } from '../helpers'; + +const templates = [ + { + _id: 'id1', + name: 'Template 1', + properties: [], + }, + { + _id: 'id2', + name: 'Template 2', + properties: [], + }, + { + _id: 'id3', + name: 'Template 3', + properties: [], + }, + { + _id: 'id4', + name: 'Template 4', + properties: [], + }, +]; + +const filters: Filter[] = [ + { + id: 'randomGroupId', + _id: '1', + rowId: '1', + name: 'Group 1', + subRows: [ + { + id: 'template_id2', + rowId: 'template_id2', + name: 'Template 2', + }, + { + id: 'template_id3', + rowId: 'template_id3', + name: 'Template 3', + }, + ], + }, + { + id: 'template_id1', + _id: '2', + rowId: '2', + name: 'Template 1', + }, + { + id: 'randomGroupId2', + _id: '4', + rowId: '4', + name: 'Group 2', + subRows: [ + { + id: 'template_id5', + rowId: 'template_id5', + name: 'Template 5', + }, + ], + }, + { + id: 'template_id4', + _id: '3', + rowId: '3', + name: 'Template 4', + }, +]; + +export { templates, filters }; diff --git a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts index 85e5fbf772..ccead16044 100644 --- a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts +++ b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts @@ -4,68 +4,7 @@ import { updateFilters, sanitizeFilters, } from '../helpers'; - -const templates = [ - { - _id: 'id1', - name: 'Template 1', - properties: [], - }, - { - _id: 'id2', - name: 'Template 2', - properties: [], - }, - { - _id: 'id3', - name: 'Template 3', - properties: [], - }, - { - _id: 'id4', - name: 'Template 4', - properties: [], - }, -]; - -const filters = [ - { - id: 'randomGroupId', - _id: '1', - name: 'Group 1', - items: [ - { - id: 'template_id2', - name: 'Template 2', - }, - { - id: 'template_id3', - name: 'Template 3', - }, - ], - }, - { - id: 'template_id1', - _id: '2', - name: 'Template 1', - }, - { - id: 'randomGroupId2', - _id: '4', - name: 'Group 2', - items: [ - { - id: 'template_id5', - name: 'Template 5', - }, - ], - }, - { - id: 'template_id4', - _id: '3', - name: 'Template 4', - }, -]; +import { filters, templates } from './fixtures'; describe('Filters helpers', () => { describe('filterAvailableTemplates', () => { @@ -73,7 +12,9 @@ describe('Filters helpers', () => { let result = filterAvailableTemplates(templates); expect(result).toEqual(templates); - result = filterAvailableTemplates(templates, [{ id: 'id2', name: 'Template 2' }]); + result = filterAvailableTemplates(templates, [ + { id: 'id2', name: 'Template 2', rowId: 'id2' }, + ]); expect(result).toEqual([ { _id: 'id1', @@ -95,14 +36,16 @@ describe('Filters helpers', () => { result = filterAvailableTemplates(templates, [ { id: 'someRandomId', + rowId: 'someRandomId', name: 'A group', - items: [ - { id: 'id1', name: 'Template 1' }, - { id: 'id4', name: 'Template 4' }, + subRows: [ + { rowId: 'id1', id: 'id1', name: 'Template 1' }, + { rowId: 'id4', id: 'id4', name: 'Template 4' }, ], }, { id: 'id2', + rowId: 'id2', name: 'Template 2', }, ]); @@ -118,19 +61,22 @@ describe('Filters helpers', () => { }); describe('deleteFilters', () => { - it('should remove from the filters list based on id', () => { + it('should remove from the filters list based selected rows', () => { let result = deleteFilters(filters); expect(result).toEqual(filters); - result = deleteFilters(filters, ['template_id1', 'template_id3']); + result = deleteFilters(filters, ['2', 'template_id3']); + expect(result).toEqual([ { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id2', + rowId: 'template_id2', name: 'Template 2', }, ], @@ -138,9 +84,11 @@ describe('Filters helpers', () => { { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { + rowId: 'template_id5', id: 'template_id5', name: 'Template 5', }, @@ -149,6 +97,7 @@ describe('Filters helpers', () => { { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]); @@ -156,32 +105,33 @@ describe('Filters helpers', () => { }); describe('sanitizeFilters', () => { - it('should remove _id from group items', () => { - let result = sanitizeFilters(filters); - expect(result).toEqual(filters); - - result = sanitizeFilters([ + it('should remove _id from root items moved into groups, rowIds and transform subrows into items', () => { + const result = sanitizeFilters([ { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id2', name: 'Template 2', - //this _id comes when users drang and drop a root filter inside a group + //this _id comes when users drag and drop a root filter inside a group //@ts-ignore - _id: 'erroneus id', + _id: 'rootItemId', + rowId: 'rootItemId', }, { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, { id: 'template_id1', - _id: '1', + _id: '2', + rowId: '2', name: 'Template 1', }, ]); @@ -204,7 +154,33 @@ describe('Filters helpers', () => { }, { id: 'template_id1', + _id: '2', + name: 'Template 1', + }, + ]); + }); + + it('should remove empty groups', () => { + const result = sanitizeFilters([ + { + id: 'randomGroupId', _id: '1', + rowId: '1', + name: 'Group 1', + subRows: [], + }, + { + id: 'template_id1', + _id: '2', + rowId: '2', + name: 'Template 1', + }, + ]); + + expect(result).toEqual([ + { + id: 'template_id1', + _id: '2', name: 'Template 1', }, ]); @@ -217,6 +193,7 @@ describe('Filters helpers', () => { { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, filters @@ -225,22 +202,25 @@ describe('Filters helpers', () => { result = updateFilters({ id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ rowId: 'new', name: 'new', id: 'new' }], }); expect(result).toEqual([ { id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ name: 'new', id: 'new', rowId: 'new' }], }, ]); result = updateFilters( { id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ name: 'new', id: 'new', rowId: 'new' }], }, filters ); @@ -248,8 +228,9 @@ describe('Filters helpers', () => { ...filters, { id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ name: 'new', id: 'new', rowId: 'new' }], }, ]); @@ -258,14 +239,17 @@ describe('Filters helpers', () => { id: 'randomGroupId2', _id: '4', name: 'Group 2', - items: [ + rowId: '4', + subRows: [ { id: 'template_id5', name: 'Template 5', + rowId: 'template_id5', }, { id: 'template_id6', name: 'Template 6', + rowId: 'template_id6', }, ], }, @@ -276,14 +260,17 @@ describe('Filters helpers', () => { id: 'randomGroupId', _id: '1', name: 'Group 1', - items: [ + rowId: '1', + subRows: [ { id: 'template_id2', name: 'Template 2', + rowId: 'template_id2', }, { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, @@ -291,25 +278,30 @@ describe('Filters helpers', () => { id: 'template_id1', _id: '2', name: 'Template 1', + rowId: '2', }, { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { id: 'template_id5', name: 'Template 5', + rowId: 'template_id5', }, { id: 'template_id6', name: 'Template 6', + rowId: 'template_id6', }, ], }, { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]); @@ -318,11 +310,13 @@ describe('Filters helpers', () => { { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, @@ -332,33 +326,39 @@ describe('Filters helpers', () => { { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, { id: 'template_id1', _id: '2', + rowId: '2', name: 'Template 1', }, { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { id: 'template_id5', name: 'Template 5', + rowId: 'template_id5', }, ], }, { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]); diff --git a/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx b/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx index 607ae42214..d122a99742 100644 --- a/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx +++ b/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx @@ -4,11 +4,11 @@ import { IncomingHttpHeaders } from 'http'; import { useLoaderData, LoaderFunction } from 'react-router-dom'; import { useAtomValue } from 'jotai'; import { intersectionBy, keyBy, merge, values } from 'lodash'; -import { ColumnDef, Row, createColumnHelper } from '@tanstack/react-table'; +import { Row, createColumnHelper } from '@tanstack/react-table'; import { Translate, I18NApi, t } from 'app/I18N'; import { RequestParams } from 'app/utils/RequestParams'; import { settingsAtom } from 'app/V2/atoms/settingsAtom'; -import { Button, Table_deprecated as Table, ConfirmationModal } from 'V2/Components/UI'; +import { Button, Table, ConfirmationModal } from 'V2/Components/UI'; import { useApiCaller } from 'V2/CustomHooks/useApiCaller'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; import { LanguageSchema } from 'shared/types/commonTypes'; @@ -24,10 +24,14 @@ import { LanguageLabel, } from './components/TableComponents'; +type TableLanguages = LanguageSchema & { rowId: string }; +const columnHelper = createColumnHelper(); + const languagesListLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => I18NApi.getLanguages(new RequestParams({}, headers)); + // eslint-disable-next-line max-statements const LanguagesList = () => { const { languages: collectionLanguages = [] } = useAtomValue(settingsAtom); @@ -41,9 +45,10 @@ const LanguagesList = () => { const notInstalledLanguages = availableLanguages.filter( l => !collectionLanguages.find(cl => cl.key === l.key) ); - const languages = values( + + const languages: TableLanguages[] = values( merge(keyBy(installedLanguages, 'key'), keyBy(collectionLanguages, 'key')) - ); + ).map(lang => ({ ...lang, rowId: lang._id! })); const handleAction = ( @@ -121,33 +126,31 @@ const LanguagesList = () => { ); }; - // Helper typed as any because of https://github.com/TanStack/table/issues/4224 - const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor('label', { id: 'label', header: LabelHeader, cell: LanguageLabel, meta: { headerClassName: 'w-9/12' }, - }) as ColumnDef, + }), columnHelper.accessor('default', { header: DefaultHeader, cell: DefaultButton, enableSorting: false, meta: { action: setDefaultLanguage, headerClassName: 'text-center w-1/12' }, - }) as ColumnDef, + }), columnHelper.accessor('key', { header: ResetHeader, cell: ResetButton, enableSorting: false, meta: { action: resetModal, headerClassName: 'text-center w-1/12' }, - }) as ColumnDef, + }), columnHelper.accessor('_id', { header: UninstallHeader, cell: UninstallButton, enableSorting: false, meta: { action: uninstallModal, headerClassName: 'text-center w-1/12' }, - }) as ColumnDef, + }), ]; return ( @@ -160,11 +163,15 @@ const LanguagesList = () => {
- +
Active languages} - initialState={{ sorting: [{ id: 'label', desc: false }] }} + header={ + + Active languages + + } + defaultSorting={[{ id: 'label', desc: false }]} /> @@ -195,4 +202,5 @@ const LanguagesList = () => { ); }; +export type { TableLanguages }; export { LanguagesList, languagesListLoader }; diff --git a/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx index 7664f4a8c0..788d67495d 100644 --- a/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx @@ -4,9 +4,9 @@ import { StarIcon } from '@heroicons/react/20/solid'; import { Translate } from 'app/I18N'; import { Button } from 'V2/Components/UI/Button'; import { CellContext } from '@tanstack/react-table'; -import { LanguageSchema } from 'shared/types/commonTypes'; +import { TableLanguages } from '../LanguagesList'; -const DefaultButton = ({ cell, column }: CellContext) => ( +const DefaultButton = ({ cell, column }: CellContext) => ( ); -const UninstallButton = ({ cell, column }: CellContext) => +const UninstallButton = ({ cell, column }: CellContext) => !cell.row.original.default ? (
{ - setLinkChanges(data); + data={linkState} + onChange={({ rows, selectedRows }) => { + setLinkState(rows); + setSelectedLinks(selectedRows); }} - title={Menu} - subRowsKey="sublinks" - onSelection={setSelectedLinks} + header={ + + Menu + + } /> - - {selectedLinks.length > 0 && ( + + {Object.keys(selectedLinks).length > 0 && (
- Selected {selectedLinks.length} of{' '} - {links?.reduce( - (acc, link) => acc + (link.type === 'group' ? (link.sublinks?.length || 1) + 1 : 1), + Selected {Object.keys(selectedLinks).length}  + of  + {linkState.reduce( + (acc, link) => acc + (link.type === 'group' ? (link.subRows?.length || 1) + 1 : 1), 0 )}
)} - {selectedLinks.length === 0 && ( + {Object.keys(selectedLinks).length === 0 && (
); }; -export { MenuForm }; +export { MenuForm, updateLinks }; diff --git a/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx b/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx index 9ceec64ba4..715ee45db7 100644 --- a/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx @@ -1,12 +1,11 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { Translate } from 'app/I18N'; -import { CellContext, ColumnDef, createColumnHelper } from '@tanstack/react-table'; -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; -import { EmbededButton, Button } from 'app/V2/Components/UI'; -import { ClientSettingsLinkSchema } from 'app/apiResponseTypes'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; +import { Button } from 'app/V2/Components/UI'; +import { Link } from '../shared'; -const EditButton = ({ cell, column }: CellContext) => ( +const EditButton = ({ cell, column }: CellContext) => (
Pages} - onSelection={setSelectedPages} - initialState={{ sorting: [{ id: 'title', desc: false }] }} + enableSelections + header={ + + Pages + + } + onChange={({ selectedRows }) => { + setSelectedPages(pages.filter(page => page.rowId in selectedRows)); + }} + defaultSorting={[{ id: 'title', desc: false }]} /> @@ -146,4 +153,6 @@ const PagesList = () => { ); }; + +export type { TablePage }; export { PagesList, pagesListLoader }; diff --git a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx index 95229f7718..46e5e627e1 100644 --- a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx +++ b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx @@ -2,19 +2,19 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { kebabCase } from 'lodash'; -import { CellContext, Row } from '@tanstack/react-table'; -import { Page } from 'app/V2/shared/types'; +import { CellContext } from '@tanstack/react-table'; import { Button, Pill } from 'app/V2/Components/UI'; import { Translate } from 'app/I18N'; +import { TablePage } from '../PagesList'; const getPageUrl = (sharedId: string, title: string) => `page/${sharedId}/${kebabCase(title)}`; const EntityViewHeader = () => Entity Page; const TitleHeader = () => Title; const UrlHeader = () => URL; -const ActionHeader = () => Action; +const ActionHeader = () => Action; -const ActionCell = ({ cell }: CellContext) => { +const ActionCell = ({ cell }: CellContext) => { const pageUrl = getPageUrl(cell.getValue(), cell.row.original.title); const isEntityView = cell.row.original.entityView; @@ -38,7 +38,7 @@ const ActionCell = ({ cell }: CellContext) => { ); }; -const YesNoPill = ({ cell }: CellContext) => { +const YesNoPill = ({ cell }: CellContext) => { const { color, label }: { color: 'primary' | 'gray'; label: React.ReactElement } = cell.getValue() ? { color: 'primary', label: Yes } : { color: 'gray', label: No }; @@ -46,19 +46,18 @@ const YesNoPill = ({ cell }: CellContext) => { return {label}; }; -const UrlCell = ({ cell }: CellContext) => { +const UrlCell = ({ cell }: CellContext) => { const sharedId = cell.getValue(); const { title } = cell.row.original; const url = `/${getPageUrl(sharedId, title)}`; return url; }; -const List = ({ items }: { items: Row[] }) => ( +const List = ({ items }: { items: TablePage[] }) => (
    - {items.map(item => { - const page = item.original; - return
  • {page.title}
  • ; - })} + {items.map(item => ( +
  • {item.title}
  • + ))}
); diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx index 40fd3eb210..8fd9250b1d 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx @@ -7,26 +7,21 @@ import { Row } from '@tanstack/react-table'; import { useSetAtom, useAtomValue } from 'jotai'; import { Translate } from 'app/I18N'; import * as relationshipTypesAPI from 'app/V2/api/relationshiptypes'; -import { ClientRelationshipType, Template } from 'app/apiResponseTypes'; +import { Template } from 'app/apiResponseTypes'; import { notificationAtom, templatesAtom } from 'app/V2/atoms'; import { relationshipTypesAtom } from 'app/V2/atoms/relationshipTypes'; -import { - Button, - Table_deprecated as Table, - Sidepanel, - ConfirmationModal, -} from 'app/V2/Components/UI'; +import { Button, Table, Sidepanel, ConfirmationModal } from 'app/V2/Components/UI'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; -import { columns, TableRelationshipType } from './components/TableComponents'; +import { columns, Relationships, TableRelationshipType } from './components/TableComponents'; import { Form } from './components/Form'; const relationshipTypesLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => - relationshipTypesAPI.get(headers); + (await relationshipTypesAPI.get(headers)).map(rel => ({ ...rel, rowId: rel._id })); const RelationshipTypes = () => { - const relationshipTypes = useLoaderData() as ClientRelationshipType[]; + const relationshipTypes = useLoaderData() as Relationships[]; const revalidator = useRevalidator(); const [isSidepanelOpen, setIsSidepanelOpen] = useState(false); @@ -35,12 +30,12 @@ const RelationshipTypes = () => { const setRelationshipTypes = useSetAtom(relationshipTypesAtom); const templates = useAtomValue(templatesAtom); - interface formType extends Omit { + interface formType extends Omit { _id?: string; } - const [formValues, setFormValues] = useState({} as ClientRelationshipType); + const [formValues, setFormValues] = useState({} as Relationships); - const [selectedItems, setSelectedItems] = useState[]>([]); + const [selectedItems, setSelectedItems] = useState([]); const [tableRelationshipTypes, setTableRelationshipTypes] = useState([]); useEffect(() => { @@ -57,6 +52,7 @@ const RelationshipTypes = () => { return { ...relationshipType, + rowId: relationshipType._id, templates: templatesUsingIt, disableRowSelection: Boolean(templatesUsingIt.length), }; @@ -64,19 +60,20 @@ const RelationshipTypes = () => { ); }, [relationshipTypes, templates]); - const edit = (row: Row) => { + const edit = (row: Row) => { setFormValues(row.original); setIsSidepanelOpen(true); }; const add = () => { - setFormValues({ name: '' }); + setFormValues({ name: '', rowId: 'NEW_REL' }); setIsSidepanelOpen(true); }; - const submit = async (submitedData: ClientRelationshipType) => { + const submit = async (submitedData: Relationships) => { + const { rowId, ...data } = submitedData; try { - await relationshipTypesAPI.save(submitedData); + await relationshipTypesAPI.save(data); setNotifications({ type: 'success', text: Updated, @@ -96,7 +93,7 @@ const RelationshipTypes = () => { const deleteSelected = async () => { try { - await relationshipTypesAPI.deleteRelationtypes(selectedItems.map(item => item.original._id)); + await relationshipTypesAPI.deleteRelationtypes(selectedItems.map(item => item._id)); setNotifications({ type: 'success', text: Updated, @@ -119,12 +116,20 @@ const RelationshipTypes = () => { - - enableSelection +
Relationship types} - onSelection={setSelectedItems} + header={ + + Relationship types + + } + onChange={({ selectedRows }) => { + setSelectedItems( + tableRelationshipTypes.filter(relationship => relationship.rowId in selectedRows) + ); + }} /> @@ -165,7 +170,7 @@ const RelationshipTypes = () => { withOverlay >
setIsSidepanelOpen(false)} currentTypes={relationshipTypes} submit={submit} @@ -179,7 +184,7 @@ const RelationshipTypes = () => { body={
    {selectedItems.map(item => ( -
  • {item.original.name}
  • +
  • {item.name}
  • ))}
} diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx index 1a442b6212..08642ba9e2 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx @@ -4,14 +4,14 @@ import React from 'react'; import { Translate } from 'app/I18N'; import { InputField } from 'app/V2/Components/Forms'; import { useForm } from 'react-hook-form'; -import { ClientRelationshipType } from 'app/apiResponseTypes'; import { Button, Card } from 'app/V2/Components/UI'; +import { Relationships } from './TableComponents'; interface FormProps { closePanel: () => void; - relationtype?: ClientRelationshipType; - submit: (formValues: ClientRelationshipType) => void; - currentTypes: ClientRelationshipType[]; + relationtype?: Relationships; + submit: (formValues: Relationships) => void; + currentTypes: Relationships[]; } const Form = ({ closePanel, submit, relationtype, currentTypes }: FormProps) => { @@ -19,7 +19,7 @@ const Form = ({ closePanel, submit, relationtype, currentTypes }: FormProps) => register, handleSubmit, formState: { errors }, - } = useForm({ + } = useForm({ values: relationtype, mode: 'onSubmit', }); @@ -47,7 +47,7 @@ const Form = ({ closePanel, submit, relationtype, currentTypes }: FormProps) => -
+
System translations} - initialState={{ sorting: [{ id: 'label', desc: false }] }} + header={ + + System translations + + } + defaultSorting={[{ id: 'label', desc: false }]} />
- +
Content translations} - initialState={{ sorting: [{ id: 'label', desc: false }] }} + header={ + + Content translations + + } + defaultSorting={[{ id: 'label', desc: false }]} /> @@ -93,4 +105,5 @@ const TranslationsList = () => { ); }; +export type { TranslationContext }; export { TranslationsList, translationsListLoader }; diff --git a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx index 6e3600c9b6..3d12001da0 100644 --- a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { CellContext } from '@tanstack/react-table'; import { Translate } from 'app/I18N'; -import { ClientTranslationContextSchema } from 'app/istore'; import { Button, Pill } from 'V2/Components/UI'; +import { TranslationContext } from '../TranslationsList'; const LabelHeader = () => Name; const TypeHeader = () => Type; -const ActionHeader = () => Action; +const ActionHeader = () => Action; -const RenderButton = ({ cell }: CellContext) => ( +const RenderButton = ({ cell }: CellContext) => (
Users} - enableSelection - onSelection={setSelectedUsers} - initialState={{ sorting: [{ id: 'username', desc: false }] }} + columns={usersTableColumns} + header={ + + Users + + } + enableSelections + onChange={({ selectedRows }) => { + setSelectedUsers(() => users.filter(user => user.rowId in selectedRows)); + }} + defaultSorting={[{ id: 'username', desc: false }]} /> Groups}> - - columns={groupsTableColumns} +
Groups} - enableSelection - onSelection={setSelectedGroups} - initialState={{ sorting: [{ id: 'name', desc: false }] }} + columns={groupsTableColumns} + header={ + + Groups + + } + enableSelections + onChange={({ selectedRows }) => { + setSelectedGroups(() => groups.filter(group => group.rowId in selectedRows)); + }} + defaultSorting={[{ id: 'name', desc: false }]} /> @@ -177,7 +188,7 @@ const Users = () => { {activeTab === 'Users' ? ( { /> ) : ( { const usersLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => { - const users = await usersAPI.get(headers); - const groups = await usersAPI.getUserGroups(headers); + const users = (await usersAPI.get(headers)).map(user => ({ ...user, rowId: user._id! })); + const groups = (await usersAPI.getUserGroups(headers)).map(group => ({ + ...group, + rowId: group._id!, + })); return { users, groups }; }; diff --git a/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx b/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx index c3c3ab9ddb..faa6286c12 100644 --- a/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx +++ b/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx @@ -3,27 +3,21 @@ import React from 'react'; import { useForm } from 'react-hook-form'; import { useFetcher } from 'react-router-dom'; import { Translate } from 'app/I18N'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; import { Button, Card, Sidepanel } from 'V2/Components/UI'; import { InputField, MultiSelect } from 'V2/Components/Forms'; import { UserGroupSchema } from 'shared/types/userGroupType'; +import { User, Group } from '../types'; interface GroupFormSidepanelProps { showSidepanel: boolean; setShowSidepanel: React.Dispatch>; - setSelected: React.Dispatch< - React.SetStateAction - >; - selectedGroup?: ClientUserGroupSchema; - users?: ClientUserSchema[]; - groups?: ClientUserGroupSchema[]; + setSelected: React.Dispatch>; + selectedGroup?: Group; + users?: User[]; + groups?: Group[]; } -const isUnique = ( - name: string, - selectedGroup?: ClientUserGroupSchema, - userGroups?: ClientUserGroupSchema[] -) => +const isUnique = (name: string, selectedGroup?: Group, userGroups?: Group[]) => !userGroups?.find( userGroup => userGroup._id !== selectedGroup?._id && diff --git a/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx b/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx index f3215da900..ddad1b11c9 100644 --- a/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx +++ b/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx @@ -1,19 +1,14 @@ import React from 'react'; -import { Row } from '@tanstack/react-table'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; +import { User, Group } from '../types'; -const ListOfItems = ({ - items, -}: { - items: Row[] | Row[]; -}) => ( -
    +const ListOfItems = ({ items }: { items: User[] | Group[] }) => ( +
      {items.length ? items.map(item => - 'username' in item.original ? ( -
    • {item.original.username}
    • + 'username' in item ? ( +
    • {item.username}
    • ) : ( -
    • {item.original.name}
    • +
    • {item.name}
    • ) ) : null} diff --git a/app/react/V2/Routes/Settings/Users/components/PermissionsListModal.tsx b/app/react/V2/Routes/Settings/Users/components/PermissionsListModal.tsx index a0a4f86e02..90499d7803 100644 --- a/app/react/V2/Routes/Settings/Users/components/PermissionsListModal.tsx +++ b/app/react/V2/Routes/Settings/Users/components/PermissionsListModal.tsx @@ -5,7 +5,7 @@ import { Tooltip } from 'flowbite-react'; import { CheckIcon, XMarkIcon } from '@heroicons/react/20/solid'; import { UserPlusIcon } from '@heroicons/react/24/outline'; import { Translate, t } from 'app/I18N'; -import { Button, Modal, Table_deprecated as Table } from 'app/V2/Components/UI'; +import { Button, Modal, Table } from 'app/V2/Components/UI'; type Level = 'none' | 'partial' | 'full'; @@ -14,6 +14,7 @@ interface PermissionByRole { admin: Level; editor: Level; collaborator: Level; + rowId: string; } interface PermissionsListModalProps { @@ -27,90 +28,105 @@ const permissionsByRole: PermissionByRole[] = [ admin: 'full', editor: 'full', collaborator: 'full', + rowId: '1', }, { action: 'Create table of contents', admin: 'full', editor: 'full', collaborator: 'full', + rowId: '2', }, { action: 'View entities', admin: 'full', editor: 'full', collaborator: 'partial', + rowId: '3', }, { action: 'Edit metadata of entities', admin: 'full', editor: 'full', collaborator: 'partial', + rowId: '4', }, { action: 'Delete entities and documents', admin: 'full', editor: 'full', collaborator: 'partial', + rowId: '5', }, { action: 'Share edit access with other users', admin: 'full', editor: 'full', collaborator: 'partial', + rowId: '6', }, { action: 'Create relationships and references', admin: 'full', editor: 'full', collaborator: 'partial', + rowId: '7', }, { action: 'Share entities with the public', admin: 'full', editor: 'full', collaborator: 'none', + rowId: '8', }, { action: 'Manage site settings and configuration', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '9', }, { action: 'Add/delete users and assign roles', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '10', }, { action: 'Configure filters', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '11', }, { action: 'Add/edit translations', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '12', }, { action: 'Configure templates ', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '13', }, { action: 'Create and edit thesauri', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '14', }, { action: 'Create relationship types', admin: 'full', editor: 'none', collaborator: 'none', + rowId: '15', }, ]; @@ -185,7 +201,7 @@ const PermissionsListModal = ({ showModal, closeModal }: PermissionsListModalPro - data={permissionsByRole} columns={tableColumns} /> +
); -const EditUserButton = ({ cell }: CellContext) => { +const EditUserButton = ({ cell }: CellContext) => { const selectedUser = cell.row.original; return cell.column.columnDef.meta?.action?.(selectedUser)} />; }; -const EditUserGroupButton = ({ cell }: CellContext) => { +const EditUserGroupButton = ({ cell }: CellContext) => { const selectedUserGroup = cell.row.original; return cell.column.columnDef.meta?.action?.(selectedUserGroup)} />; }; -const getUsersColumns = (editButtonAction: (user: ClientUserSchema) => void) => { - const columnHelper = createColumnHelper(); - return [ - columnHelper.accessor('username', { - header: UsernameHeader, - cell: UsernameCell, - meta: { headerClassName: 'w-1/3' }, - }), - columnHelper.accessor('using2fa', { - header: ProtectionHeader, - cell: ProtectionPill, - meta: { headerClassName: 'w-0' }, - }), - columnHelper.accessor('role', { - header: RoleHeader, - cell: RolePill, - meta: { headerClassName: 'w-0' }, - }), - columnHelper.accessor('groups', { - header: GroupsHeader, - cell: GroupsPill, - meta: { headerClassName: 'w-2/3' }, - }), - columnHelper.display({ - id: '1', - header: ActionHeader, - cell: EditUserButton, - meta: { action: editButtonAction, headerClassName: 'sr-only invisible bg-gray-50' }, - enableSorting: false, - }), - ]; -}; +const getUsersColumns = (editButtonAction: (user: User) => void) => [ + userColumns.accessor('username', { + header: UsernameHeader, + cell: UsernameCell, + meta: { headerClassName: 'w-1/3' }, + }), + userColumns.accessor('using2fa', { + header: ProtectionHeader, + cell: ProtectionPill, + meta: { headerClassName: 'w-0' }, + }), + userColumns.accessor('role', { + header: RoleHeader, + cell: RolePill, + meta: { headerClassName: 'w-0' }, + }), + userColumns.accessor('groups', { + header: GroupsHeader, + cell: GroupsPill, + meta: { headerClassName: 'w-2/3' }, + }), + userColumns.display({ + id: '1', + header: ActionHeader, + cell: EditUserButton, + meta: { action: editButtonAction }, + enableSorting: false, + }), +]; -const getGroupsColumns = (editButtonAction: (group: ClientUserGroupSchema) => void) => { - const columnHelper = createColumnHelper(); - return [ - columnHelper.accessor('name', { - header: GroupNameHeader, - meta: { headerClassName: 'w-1/4' }, - }), - columnHelper.accessor('members', { - header: MembersHeader, - cell: MembersPill, - enableSorting: false, - meta: { headerClassName: 'w-3/4' }, - }), - columnHelper.display({ - id: '1', - header: ActionHeader, - cell: EditUserGroupButton, - meta: { action: editButtonAction, headerClassName: 'sr-only invisible bg-gray-50' }, - enableSorting: false, - }), - ]; -}; +const getGroupsColumns = (editButtonAction: (group: Group) => void) => [ + groupColumns.accessor('name', { + header: GroupNameHeader, + meta: { headerClassName: 'w-1/4' }, + }), + groupColumns.accessor('members', { + header: MembersHeader, + cell: MembersPill, + enableSorting: false, + meta: { headerClassName: 'w-3/4' }, + }), + groupColumns.display({ + id: '1', + header: ActionHeader, + cell: EditUserGroupButton, + meta: { action: editButtonAction }, + enableSorting: false, + }), +]; export { getUsersColumns, getGroupsColumns }; diff --git a/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx b/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx index be5197445d..acd63c9e65 100644 --- a/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx +++ b/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx @@ -6,25 +6,23 @@ import { useForm } from 'react-hook-form'; import { useFetcher } from 'react-router-dom'; import { FetchResponseError } from 'shared/JSONRequest'; import { t, Translate } from 'app/I18N'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; import { InputField, Select, MultiSelect } from 'V2/Components/Forms'; import { Button, Card, ConfirmationModal, Sidepanel } from 'V2/Components/UI'; import { validEmailFormat } from 'V2/shared/formatHelpers'; import { UserRole } from 'shared/types/userSchema'; import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'; import { PermissionsListModal } from './PermissionsListModal'; +import { User, Group } from '../types'; type SubmitType = 'formSubmit' | 'reset-2fa' | 'unlock-user' | 'reset-password' | undefined; interface UserFormSidepanelProps { showSidepanel: boolean; setShowSidepanel: React.Dispatch>; - setSelected: React.Dispatch< - React.SetStateAction - >; - selectedUser?: ClientUserSchema; - users?: ClientUserSchema[]; - groups?: ClientUserGroupSchema[]; + setSelected: React.Dispatch>; + selectedUser?: User; + users?: User[]; + groups?: Group[]; } const userRoles = [ @@ -36,7 +34,7 @@ const userRoles = [ }, ]; -const isUnique = (nameVal: string, selectedUser?: ClientUserSchema, users?: ClientUserSchema[]) => +const isUnique = (nameVal: string, selectedUser?: User, users?: User[]) => !users?.find( existingUser => existingUser._id !== selectedUser?._id && @@ -44,7 +42,7 @@ const isUnique = (nameVal: string, selectedUser?: ClientUserSchema, users?: Clie existingUser.email?.trim().toLowerCase() === nameVal.trim().toLowerCase()) ); -const calculateSelectedGroups = (selectedGroups: string[], groups?: ClientUserGroupSchema[]) => +const calculateSelectedGroups = (selectedGroups: string[], groups?: Group[]) => selectedGroups.map(selectedGroup => { const group = groups?.find(originalGroup => originalGroup.name === selectedGroup); return { _id: group?._id as string, name: group?.name as string }; @@ -110,7 +108,8 @@ const UserFormSidepanel = ({ password: '', role: 'collaborator', groups: [], - } as ClientUserSchema); + rowId: 'NEW', + } as User); const { register, @@ -140,7 +139,7 @@ const UserFormSidepanel = ({ } }, [fetcher]); - const formSubmit = async (data: ClientUserSchema) => { + const formSubmit = async (data: User) => { const formData = new FormData(); if (data._id) { formData.set('intent', 'edit-user'); diff --git a/app/react/V2/Routes/Settings/Users/types.ts b/app/react/V2/Routes/Settings/Users/types.ts index a46d61becf..ee77b1e402 100644 --- a/app/react/V2/Routes/Settings/Users/types.ts +++ b/app/react/V2/Routes/Settings/Users/types.ts @@ -1,3 +1,5 @@ +import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; + type FormIntent = | 'new-user' | 'edit-user' @@ -11,4 +13,7 @@ type FormIntent = | 'bulk-reset-2fa' | 'bulk-reset-password'; -export type { FormIntent }; +type User = ClientUserSchema & { rowId: string }; +type Group = ClientUserGroupSchema & { rowId: string }; + +export type { FormIntent, User, Group }; diff --git a/app/react/V2/api/pages/index.ts b/app/react/V2/api/pages/index.ts index 3a8037879e..b51f5407fd 100644 --- a/app/react/V2/api/pages/index.ts +++ b/app/react/V2/api/pages/index.ts @@ -4,7 +4,7 @@ import { RequestParams } from 'app/utils/RequestParams'; import { Page } from 'V2/shared/types'; import { FetchResponseError } from 'shared/JSONRequest'; -const get = async (language: string, headers?: IncomingHttpHeaders): Promise => { +const get = async (language: string, headers?: IncomingHttpHeaders): Promise => { try { const requestParams = new RequestParams({}, headers); api.locale(language); diff --git a/app/react/V2/api/users/index.ts b/app/react/V2/api/users/index.ts index d0368b3144..6dcb66bc3a 100644 --- a/app/react/V2/api/users/index.ts +++ b/app/react/V2/api/users/index.ts @@ -4,8 +4,9 @@ import api from 'app/utils/api'; import { RequestParams } from 'app/utils/RequestParams'; import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; -const prepareUser = (user: ClientUserSchema) => { +const prepareUser = (user: ClientUserSchema & { rowId?: string }) => { const preparedUser = { ...user }; + delete preparedUser.rowId; if (!preparedUser.password) { delete preparedUser.password; @@ -74,9 +75,13 @@ const deleteUser = async ( } }; -const saveGroup = async (group: ClientUserGroupSchema, headers?: IncomingHttpHeaders) => { +const saveGroup = async ( + group: ClientUserGroupSchema & { rowId?: string }, + headers?: IncomingHttpHeaders +) => { try { - const requestParams = new RequestParams(group, headers); + const { rowId, ...groupToSave } = group; + const requestParams = new RequestParams(groupToSave, headers); const response = await api.post('usergroups', requestParams); return response.json; } catch (e) { @@ -169,7 +174,7 @@ const reset2FA = async ( } }; -const get = async (headers?: IncomingHttpHeaders) => { +const get = async (headers?: IncomingHttpHeaders): Promise => { try { const requestParams = new RequestParams({}, headers); const response = await UsersAPI.get(requestParams); @@ -189,7 +194,7 @@ const getCurrentUser = async (headers?: IncomingHttpHeaders) => { } }; -const getUserGroups = async (headers?: IncomingHttpHeaders) => { +const getUserGroups = async (headers?: IncomingHttpHeaders): Promise => { try { const requestParams = new RequestParams({}, headers); const response = await api.get('usergroups', requestParams); diff --git a/app/react/stories/DragAndDrop.stories.tsx b/app/react/stories/DragAndDrop.stories.tsx deleted file mode 100644 index 176e8513aa..0000000000 --- a/app/react/stories/DragAndDrop.stories.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { PropsWithChildren } from 'react'; -import { DndProvider } from 'react-dnd'; -import { Provider } from 'react-redux'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { Meta, StoryObj } from '@storybook/react'; -import { ItemTypes } from 'app/V2/shared/types'; -import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers'; -import { - CardWithDnD, - CardWithRemove, - DnDClient, - DnDClientWithForm, -} from './dragAndDrop/DragAndDropComponents'; - -const meta: Meta = { - title: 'Components/DragAndDrop', - component: DnDClient, -}; - -type Story = StoryObj; - -const RenderWithProvider = ({ children }: PropsWithChildren) => ( - - {children} - -); - -const Primary: Story = { - render: args => ( - - - - ), -}; - -const WithForm: Story = { - render: args => ( - - - - ), -}; - -const Basic = { - ...Primary, - args: { - type: ItemTypes.BOX, - items: [{ name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }], - iconHandle: true, - }, -}; - -const WithItemComponent = { - ...Primary, - args: { - ...Basic.args, - itemComponent: CardWithRemove, - }, -}; - -const Nested = { - ...Primary, - args: { - ...Basic.args, - iconHandle: false, - items: [ - { name: 'Item 1', items: [{ name: 'Subitem 1' }] }, - { name: 'Item 2', items: [] }, - { name: 'Item 3', items: [] }, - ], - itemComponent: CardWithDnD, - }, -}; - -const Form = { - ...WithForm, - args: { - type: ItemTypes.BOX, - items: [{ name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }], - iconHandle: true, - }, -}; - -export { Basic, WithItemComponent, Nested, Form }; -export default meta; diff --git a/app/react/stories/Table.stories.tsx b/app/react/stories/Table.stories.tsx index 96add38582..5e30e16323 100644 --- a/app/react/stories/Table.stories.tsx +++ b/app/react/stories/Table.stories.tsx @@ -1,109 +1,275 @@ -import React from 'react'; +/* eslint-disable max-lines */ +import React, { useRef, useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; -import { Table_deprecated as Table } from 'V2/Components/UI'; -import { - StoryComponent, - type SampleSchema, - CheckboxesTableComponent, - basicColumns, - withActionsColumns, -} from './table/TableComponents'; - -const meta: Meta = { - title: 'Components/Table', - component: Table, +import { action } from '@storybook/addon-actions'; +import { Cell, createColumnHelper, SortingState } from '@tanstack/react-table'; +import { Provider } from 'react-redux'; +import { Button, Table } from 'V2/Components/UI'; +import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers'; +import { BasicData, DataWithGroups, basicData, dataWithGroups } from './table/fixtures'; + +type StoryProps = { + columnType: string; + tableData: any[]; + dnd?: { enable?: boolean; disableEditingGroups?: boolean }; + enableSelections?: boolean; + defaultSorting?: SortingState; + sortingFn?: () => void; + actionFn?: () => void; }; -type Story = StoryObj> & { args?: { showUpdates?: boolean } }; +const CustomDateCell = ({ cell }: { cell: Cell }) => ( +
{cell.renderValue()}
+); -const Primary: Story = { - render: args => , +const ActionHeader = () => Actions; + +const ActionCell = ({ cell }: { cell: Cell }) => { + const actionFn = cell.getContext().column.columnDef.meta?.action + ? cell.getContext().column.columnDef.meta?.action! + : () => {}; + + return ( + + ); }; -const Checkboxes: Story = { - render: CheckboxesTableComponent, +const basicColumnHelper = createColumnHelper(); +const nestedColumnHelper = createColumnHelper(); + +const basicColumns = [ + basicColumnHelper.accessor('title', { header: 'Title' }), + basicColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), + nestedColumnHelper.accessor('created', { header: 'Date added' }), +]; + +const nestedColumns = [ + nestedColumnHelper.accessor('title', { header: 'Title' }), + nestedColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), + nestedColumnHelper.accessor('created', { header: 'Date added' }), +]; + +const getCustomColums = (actionFn?: () => any) => [ + basicColumnHelper.accessor('title', { + header: 'Title', + meta: { contentClassName: 'bg-gray-100 text-red-700' }, + }), + basicColumnHelper.accessor('description', { + enableSorting: false, + header: 'Description', + size: 200, + meta: { headerClassName: 'bg-blue-700 text-white' }, + }), + basicColumnHelper.accessor('created', { header: 'Date added', cell: CustomDateCell }), + basicColumnHelper.display({ + id: 'action', + header: ActionHeader, + cell: ActionCell, + minSize: 25, + size: 0, + meta: { action: actionFn || action('accepted') }, + }), +]; + +const getColumns = (type: string, actionFn?: () => any) => { + switch (type) { + case 'nested': + return nestedColumns; + + case 'custom': + return getCustomColums(actionFn); + + default: + return basicColumns; + } }; -const Basic: Story = { - ...Primary, - args: { - title: 'Table name', - footer: * Table footer, - columns: basicColumns, - data: [ - { title: 'Entity 2', created: 2, description: 'Short text' }, - { - title: 'Entity 1', - created: 1, - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vel efficitur quam. Donec feugiat at libero at rutrum.', - children: [ - { - title: 'Entity a', - created: 4, - description: 'Donec feugiat at libero at rutrum.', - }, - { - title: 'Entity b', - created: 5, - description: 'Phasellus vel efficitur quam.', - }, - ], - }, - { - title: 'Entity 3', - created: 3, - description: 'Morbi congue et justo vitae congue. Vivamus porttitor et leo vitae efficitur', - }, - ], - setSorting: undefined, - }, +const StoryComponent = ({ + columnType, + tableData, + dnd, + enableSelections, + defaultSorting, + sortingFn, + actionFn, +}: StoryProps) => { + const [dataState, setDataState] = useState(tableData); + const [selected, setSelected] = useState({}); + const [sorting, setSorting] = useState([]); + const currentDataState = useRef(tableData); + const currentSelections = useRef({}); + const [itemCounter, setItemCounter] = useState(1); + + const columns = getColumns(columnType, actionFn); + + return ( +
+
+
{ + currentDataState.current = rows; + currentSelections.current = selectedRows; + setSorting(sortingState); + }} + sortingFn={sortingFn} + dnd={dnd} + enableSelections={enableSelections} + header={ +
+

Table heading

+

{sorting.length ? `Sorted by ${sorting[0].id}` : 'No sorting'}

+
+ } + footer={

My table footer

} + /> +
+ + + + +
+ +
+
+

Row state:

+
{dataState.map(ds => `${ds.title} `)}
+
+
+
+

Subrow state:

+
+ {dataState.map((ds: DataWithGroups) => + ds.subRows?.map(subRow => ( + + |{ds.title} - {subRow.title}| + + )) + )} +
+
+
+
+

Selected rows:

+
+ {dataState + .filter(ds => ds.rowId in selected) + .map(ds => ( + {ds.title} + ))} +
+
+
+
+

Selected subRows:

+
+ {dataState.map((ds: DataWithGroups) => + ds.subRows + ?.filter(subRow => subRow.rowId in selected) + .map(subRow => {subRow.title}) + )} +
+
+ + ); }; -const WithInitialState: Story = { - ...Primary, - args: { - ...Basic.args, - initialState: { sorting: [{ id: 'description', desc: true }] }, - }, +const meta: Meta = { + title: 'Components/TableV2', + component: StoryComponent, }; -const WithActions: Story = { - ...Primary, - args: { - ...Basic.args, - columns: withActionsColumns, - }, +type Story = StoryObj; + +const Primary: Story = { + render: args => ( + + + + ), }; -const WithCheckboxes = { - ...Checkboxes, +const Basic = { + ...Primary, args: { - ...Basic.args, + dnd: { enable: true, disableEditingGroups: false }, + enableSelections: true, + defaultSorting: undefined, + tableData: basicData, + columnType: 'basic', + sortingFn: undefined, + actionFn: undefined, }, }; -const WithDnD: Story = { +const Nested = { ...Primary, args: { ...Basic.args, - draggableRows: true, - initialState: { sorting: [{ id: 'description', desc: true }] }, - showUpdates: true, + tableData: dataWithGroups, + columnType: 'nested', }, }; -const NestedDnD: Story = { +const Custom = { ...Primary, args: { ...Basic.args, - subRowsKey: 'children', - showUpdates: true, - draggableRows: true, - allowEditGroupsWithDnD: true, + enableSelections: false, + dnd: undefined, + columnType: 'custom', }, }; -export { Basic, WithActions, WithCheckboxes, WithInitialState, WithDnD, NestedDnD }; - +export { Basic, Nested, Custom }; export default meta; diff --git a/app/react/stories/TableV2.stories.tsx b/app/react/stories/TableV2.stories.tsx deleted file mode 100644 index 5e30e16323..0000000000 --- a/app/react/stories/TableV2.stories.tsx +++ /dev/null @@ -1,275 +0,0 @@ -/* eslint-disable max-lines */ -import React, { useRef, useState } from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; -import { Cell, createColumnHelper, SortingState } from '@tanstack/react-table'; -import { Provider } from 'react-redux'; -import { Button, Table } from 'V2/Components/UI'; -import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers'; -import { BasicData, DataWithGroups, basicData, dataWithGroups } from './table/fixtures'; - -type StoryProps = { - columnType: string; - tableData: any[]; - dnd?: { enable?: boolean; disableEditingGroups?: boolean }; - enableSelections?: boolean; - defaultSorting?: SortingState; - sortingFn?: () => void; - actionFn?: () => void; -}; - -const CustomDateCell = ({ cell }: { cell: Cell }) => ( -
{cell.renderValue()}
-); - -const ActionHeader = () => Actions; - -const ActionCell = ({ cell }: { cell: Cell }) => { - const actionFn = cell.getContext().column.columnDef.meta?.action - ? cell.getContext().column.columnDef.meta?.action! - : () => {}; - - return ( - - ); -}; - -const basicColumnHelper = createColumnHelper(); -const nestedColumnHelper = createColumnHelper(); - -const basicColumns = [ - basicColumnHelper.accessor('title', { header: 'Title' }), - basicColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), - nestedColumnHelper.accessor('created', { header: 'Date added' }), -]; - -const nestedColumns = [ - nestedColumnHelper.accessor('title', { header: 'Title' }), - nestedColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), - nestedColumnHelper.accessor('created', { header: 'Date added' }), -]; - -const getCustomColums = (actionFn?: () => any) => [ - basicColumnHelper.accessor('title', { - header: 'Title', - meta: { contentClassName: 'bg-gray-100 text-red-700' }, - }), - basicColumnHelper.accessor('description', { - enableSorting: false, - header: 'Description', - size: 200, - meta: { headerClassName: 'bg-blue-700 text-white' }, - }), - basicColumnHelper.accessor('created', { header: 'Date added', cell: CustomDateCell }), - basicColumnHelper.display({ - id: 'action', - header: ActionHeader, - cell: ActionCell, - minSize: 25, - size: 0, - meta: { action: actionFn || action('accepted') }, - }), -]; - -const getColumns = (type: string, actionFn?: () => any) => { - switch (type) { - case 'nested': - return nestedColumns; - - case 'custom': - return getCustomColums(actionFn); - - default: - return basicColumns; - } -}; - -const StoryComponent = ({ - columnType, - tableData, - dnd, - enableSelections, - defaultSorting, - sortingFn, - actionFn, -}: StoryProps) => { - const [dataState, setDataState] = useState(tableData); - const [selected, setSelected] = useState({}); - const [sorting, setSorting] = useState([]); - const currentDataState = useRef(tableData); - const currentSelections = useRef({}); - const [itemCounter, setItemCounter] = useState(1); - - const columns = getColumns(columnType, actionFn); - - return ( -
-
-
{ - currentDataState.current = rows; - currentSelections.current = selectedRows; - setSorting(sortingState); - }} - sortingFn={sortingFn} - dnd={dnd} - enableSelections={enableSelections} - header={ -
-

Table heading

-

{sorting.length ? `Sorted by ${sorting[0].id}` : 'No sorting'}

-
- } - footer={

My table footer

} - /> -
- - - - -
- -
-
-

Row state:

-
{dataState.map(ds => `${ds.title} `)}
-
-
-
-

Subrow state:

-
- {dataState.map((ds: DataWithGroups) => - ds.subRows?.map(subRow => ( - - |{ds.title} - {subRow.title}| - - )) - )} -
-
-
-
-

Selected rows:

-
- {dataState - .filter(ds => ds.rowId in selected) - .map(ds => ( - {ds.title} - ))} -
-
-
-
-

Selected subRows:

-
- {dataState.map((ds: DataWithGroups) => - ds.subRows - ?.filter(subRow => subRow.rowId in selected) - .map(subRow => {subRow.title}) - )} -
-
- - ); -}; - -const meta: Meta = { - title: 'Components/TableV2', - component: StoryComponent, -}; - -type Story = StoryObj; - -const Primary: Story = { - render: args => ( - - - - ), -}; - -const Basic = { - ...Primary, - args: { - dnd: { enable: true, disableEditingGroups: false }, - enableSelections: true, - defaultSorting: undefined, - tableData: basicData, - columnType: 'basic', - sortingFn: undefined, - actionFn: undefined, - }, -}; - -const Nested = { - ...Primary, - args: { - ...Basic.args, - tableData: dataWithGroups, - columnType: 'nested', - }, -}; - -const Custom = { - ...Primary, - args: { - ...Basic.args, - enableSelections: false, - dnd: undefined, - columnType: 'custom', - }, -}; - -export { Basic, Nested, Custom }; -export default meta; diff --git a/app/react/stories/dragAndDrop/DragAndDropComponents.tsx b/app/react/stories/dragAndDrop/DragAndDropComponents.tsx deleted file mode 100644 index 290ff34410..0000000000 --- a/app/react/stories/dragAndDrop/DragAndDropComponents.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { FC, useCallback } from 'react'; -import { TrashIcon } from '@heroicons/react/20/solid'; -import { Button } from 'app/V2/Components/UI'; -import { - Container, - DragSource, - DraggableItem, - DropZone, - IItemComponentProps, - useDnDContext, - IDnDContext, -} from 'app/V2/Components/Layouts/DragAndDrop'; -import type { IDraggable } from 'app/V2/shared/types'; -import debounce from 'app/utils/debounce'; - -interface DnDValueExample { - name: string; - items?: DnDValueExample[]; -} - -const sampleDefaultName = (item: IDraggable) => item.value.name; - -const sourceItems: IDraggable[] = [ - { value: { name: 'Item 4' } }, - { value: { name: 'Item 5' } }, -]; - -const CardWithRemove: FC> = ({ item, context }) => ( -
-
{context.getDisplayName(item)}
- -
-); - -const CardWithDnD: FC> = ({ item, context, index }) => ( -
- - -
-); - -const DndContextState = ({ - activeItems, - context, - child = false, -}: { - activeItems: IDraggable[]; - context: IDnDContext; - child?: boolean; -}) => ( -
-
-

State Items

-
    - {activeItems.map((item: IDraggable) => ( -
  • - {context.getDisplayName(item)} - {item.value.items && ( - v)} - child - /> - )} -
  • - ))} -
-
-
-); -const DnDClient = ({ items, type, itemComponent }: any) => { - const dndContext = useDnDContext(type, { getDisplayName: sampleDefaultName }, items, sourceItems); - - return ( -
-
-
-

Active Items

- -
-
-
-

Available Items

- className="p-3 mb-2 text-sm" context={dndContext} /> -
-
-
- -
- ); -}; - -const EditableItem = ({ - dndContext, - item, - index, -}: { - dndContext: IDnDContext; - item: IDraggable; - index: number; -}) => { - const handleChange = debounce((e: React.ChangeEvent) => { - dndContext.updateItem({ ...item, value: { ...item.value, name: e.target.value } }); - }, 300); - - const debouncedChangeHandler = useCallback(handleChange, [handleChange]); - - return ( - - ); -}; -const DnDClientWithForm = ({ items, type }: any) => { - const dndContext = useDnDContext(type, { getDisplayName: sampleDefaultName }, items, sourceItems); - - return ( - <> -
-
-
-

Active Items

-
    - {dndContext.activeItems.map((item: IDraggable, index: number) => ( - - - - ))} -
- -
-
-
-

Available Items

- -
-
-
-
- - - ); -}; - -export type { DnDValueExample }; -export { - DnDClientWithForm, - DnDClient, - CardWithDnD, - CardWithRemove, - sourceItems, - sampleDefaultName, -}; diff --git a/app/react/stories/table/TableComponents.tsx b/app/react/stories/table/TableComponents.tsx deleted file mode 100644 index 7ddcab4246..0000000000 --- a/app/react/stories/table/TableComponents.tsx +++ /dev/null @@ -1,198 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { useState } from 'react'; -import { CellContext, createColumnHelper, Row } from '@tanstack/react-table'; -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; -import { - EmbededButton, - Table_deprecated as Table, - TableProps_deprecated as TableProps, -} from 'V2/Components/UI'; -import { Button } from 'V2/Components/UI/Button'; -import { get } from 'lodash'; - -type SampleSchema = { - title: string; - description: string; - created: number; - children?: SampleSchema[]; -}; - -const columnHelper = createColumnHelper(); -const CustomCell = ({ cell }: CellContext) => ( -
{cell.getValue()}
-); - -const ActionsCell = () => ( -
- - -
-); - -const StoryComponent = (props: TableProps & { showUpdates?: boolean }) => { - const [tableState, setTableState] = useState(props.data); - const [firstLoad, setFirstLoad] = useState(true); - - const onChangeHandler = (data: SampleSchema[]) => { - setTableState(data); - setFirstLoad(false); - }; - - return ( - <> -
- - columns={props.columns} - data={tableState} - title={props.title} - initialState={props.initialState} - footer={props.footer} - setSorting={props.setSorting} - draggableRows={props.draggableRows === true} - onChange={onChangeHandler} - subRowsKey={props.subRowsKey} - allowEditGroupsWithDnD={props.allowEditGroupsWithDnD} - /> -
- {props.showUpdates === true && !firstLoad && ( -
- Updated state -
    - {tableState?.map((item: SampleSchema, index: number) => { - const children = props.subRowsKey - ? (get(item, props.subRowsKey) || []).map((child: SampleSchema) => child.title) - : []; - return ( - // eslint-disable-next-line react/no-array-index-key -
  • {`${item.title} ${children.join(', ')}`}
  • - ); - })} -
-
- )} - - ); -}; - -const updatedData = [ - { title: 'Entity 2', created: 2, description: 'Short text' }, - { - title: 'Entity 3', - created: 3, - description: 'Morbi congue et justo vitae congue. Vivamus porttitor et leo vitae efficitur', - }, -]; - -const CheckboxesTableComponent = (args: TableProps) => { - const [selected1, setSelected1] = useState[]>([]); - const [selected2, setSelected2] = useState[]>([]); - const [table2Data, setTable2Data] = useState(args.data); - - return ( -
- - columns={args.columns} - data={args.data} - title="Table A" - enableSelection - onSelection={setSelected1} - draggableRows={args.draggableRows} - /> - -

Selected items for Table A: {selected1.length}

-

- Selections of Table A: {selected1.map(sel => `${sel.original.title}, `)} -

- -
- - - columns={args.columns} - data={table2Data} - title="Table B" - enableSelection - onSelection={setSelected2} - draggableRows={args.draggableRows} - /> - -

Selected items for Table B: {selected2.length}

-

- Selections of Table B: {selected2.map(sel => `${sel.original.title}, `)} -

- -
- - - -
-
- ); -}; - -const TitleCell = ({ row, getValue }: CellContext) => ( -
- - {getValue()} - - {row.getCanExpand() && ( - : } - onClick={() => row.toggleExpanded()} - color="indigo" - data-test-id="chevron-icon" - > - children - - )} -
-); - -const basicColumns = [ - columnHelper.accessor('title', { header: 'Title', id: 'title', cell: TitleCell }), - columnHelper.accessor('description', { header: 'Description' }), - columnHelper.accessor('created', { - header: 'Date added', - cell: CustomCell, - meta: { headerClassName: 'something' }, - }), -]; - -const withActionsColumns = [ - columnHelper.accessor('title', { - id: 'title', - header: 'Title', - meta: { headerClassName: 'w-2/4' }, - }), - columnHelper.accessor('created', { - id: 'created', - header: 'Date added', - meta: { headerClassName: 'w-1/4' }, - }), - columnHelper.accessor('description', { - id: 'description', - header: 'Description', - enableSorting: false, - meta: { headerClassName: 'w-1/4 bg-error-100 text-blue-600' }, - }), - columnHelper.display({ - id: 'action', - header: 'Actions', - cell: ActionsCell, - meta: { headerClassName: 'sr-only' }, - }), -]; - -export type { SampleSchema }; -export { StoryComponent, CheckboxesTableComponent, basicColumns, withActionsColumns }; diff --git a/contents/ui-translations/ar.csv b/contents/ui-translations/ar.csv index be30d3627b..053b880990 100644 --- a/contents/ui-translations/ar.csv +++ b/contents/ui-translations/ar.csv @@ -265,7 +265,6 @@ documents.,مستندات Download,تحميل Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,اسحب الملف وأفلته في هذه النافذة للتحميل -Drag items here,اسحب العناصر هنا Drag properties here,اسحب الخصائص هنا Drag row,Drag row Drop your files here to upload or,اسحب ملفاتك هنا للتحميل أو diff --git a/contents/ui-translations/en.csv b/contents/ui-translations/en.csv index 0ace762af2..9429e8df86 100644 --- a/contents/ui-translations/en.csv +++ b/contents/ui-translations/en.csv @@ -268,7 +268,6 @@ documents.,documents. Download,Download Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Drag and drop file in this window to upload -Drag items here,Drag items here Drag properties here,Drag properties here Drag row,Drag row Drop your files here to upload or,Drop your files here to upload or diff --git a/contents/ui-translations/es.csv b/contents/ui-translations/es.csv index c7fcbd3c9d..3dcdc2221f 100644 --- a/contents/ui-translations/es.csv +++ b/contents/ui-translations/es.csv @@ -264,7 +264,6 @@ documents.,documentos. Download,Descargar Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Arrastrar y soltar el archivo en esta ventana para subir. -Drag items here,Arrastra aquí Drag properties here,Arrastra las propiedades aquí Drag row,Drag row Drop your files here to upload or,Suelta los archivos aquí para cargar o diff --git a/contents/ui-translations/fr.csv b/contents/ui-translations/fr.csv index 64c7e18394..449544d733 100644 --- a/contents/ui-translations/fr.csv +++ b/contents/ui-translations/fr.csv @@ -265,7 +265,6 @@ documents.,Documents. Download,Télécharger Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Faites glisser et déposez le fichier dans cette fenêtre pour le télécharger -Drag items here,Faire glisser les éléments ici Drag properties here,Faites glisser les propriétés ici Drag row,Drag row Drop your files here to upload or,Déposez vos fichiers ici pour les télécharger ou diff --git a/contents/ui-translations/ko.csv b/contents/ui-translations/ko.csv index fedda4e47b..b5c11dc68e 100644 --- a/contents/ui-translations/ko.csv +++ b/contents/ui-translations/ko.csv @@ -266,7 +266,6 @@ documents.,문서 Download,다운로드 Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,이 창에 파일을 드래그 앤 드롭해 업로드합니다. -Drag items here,여기에 아이템을 드래그합니다. Drag properties here,여기에 속성을 드래그합니다. Drag row,Drag row Drop your files here to upload or,여기에 파일을 드롭해 업로드합니다. diff --git a/contents/ui-translations/my.csv b/contents/ui-translations/my.csv index 6fde2e2853..c1163363dc 100644 --- a/contents/ui-translations/my.csv +++ b/contents/ui-translations/my.csv @@ -266,7 +266,6 @@ documents.,စာရွက်စာတမ်းများ။ Download,ဒေါင်းလုဒ်လုပ်ရန် Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,အပ်လုဒ်လုပ်ရန် ဖိုင်ကို ဤဝင်းဒိုးထဲသို့ ဆွဲချပါ -Drag items here,ဖိုင်များကို ဒီဘက် ဆွဲရွှေ့ပါ Drag properties here,ထူးခြားချက်များကို ဒီဘက် ဆွဲရွှေ့ပါ Drag row,Drag row Drop your files here to upload or,သင့်ဖိုင်များကို အပ်လုဒ်လုပ်ရန် ဒီနေရာတွင် ချပါ သို့မဟုတ် diff --git a/contents/ui-translations/ru.csv b/contents/ui-translations/ru.csv index 1e0e5a4653..3a7edf4e77 100644 --- a/contents/ui-translations/ru.csv +++ b/contents/ui-translations/ru.csv @@ -263,7 +263,6 @@ documents.,документы. Download,Скачать Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,"Перетащите и опустите файл в это окно, чтобы загрузить" -Drag items here,Перетащите элементы сюда Drag properties here,Перетащите свойства сюда Drag row,Drag row Drop your files here to upload or,"Опустите свои файлы сюда, чтобы загрузить или" diff --git a/contents/ui-translations/th.csv b/contents/ui-translations/th.csv index 18043171ea..08d1eb94f0 100644 --- a/contents/ui-translations/th.csv +++ b/contents/ui-translations/th.csv @@ -266,7 +266,6 @@ documents.,เอกสาร. Download,ดาวน์โหลด Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,นำไฟล์มาวางในหน้าต่างนี้เพื่ออัปโหลด -Drag items here,นำรายการมาวางที่นี่ Drag properties here,นำคุณสมบัติมาวางที่นี่ Drag row,Drag row Drop your files here to upload or,นำไฟล์มาวางที่นี่เพื่ออัปโหลด หรือ diff --git a/contents/ui-translations/tr.csv b/contents/ui-translations/tr.csv index 6ce3d9f807..bfbf044151 100644 --- a/contents/ui-translations/tr.csv +++ b/contents/ui-translations/tr.csv @@ -266,7 +266,6 @@ documents.,belgeler. Download,Özel filtre için belgeler Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Yüklemek için dosyayı bu pencerede sürükleyip bırakın -Drag items here,Öğeleri buraya sürükleyin Drag properties here,Özellikleri buraya sürükleyin Drag row,Drag row Drop your files here to upload or,Dosyalarınızı yüklemek için buraya bırakın veya diff --git a/cypress/e2e/pages/__image_snapshots__/Pages Pages list should render a list with all pages names #0.png b/cypress/e2e/pages/__image_snapshots__/Pages Pages list should render a list with all pages names #0.png index e2c497dcd5ff9711e741e247da9adb07b2577e25..8a31b3865198a4a1ac2d93c4033378842330159c 100644 GIT binary patch literal 48315 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 literal 60222 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 diff --git a/cypress/e2e/pages/pages.cy.ts b/cypress/e2e/pages/pages.cy.ts index c4e0df7bd8..fc87bf4b5b 100644 --- a/cypress/e2e/pages/pages.cy.ts +++ b/cypress/e2e/pages/pages.cy.ts @@ -211,7 +211,7 @@ describe('Pages', () => { cy.contains('a', 'Settings').click(); cy.contains('a', 'Pages').click(); cy.contains('Country page'); - cy.get('[data-testid="settings-content"] [data-testid="table"]').toMatchImageSnapshot({ + cy.get('table').toMatchImageSnapshot({ disableTimersAndAnimations: true, threshold: 0.08, }); @@ -232,6 +232,7 @@ describe('Pages', () => { cy.contains('Deleted successfully'); cy.contains('Country page'); cy.contains('Page with error').should('not.exist'); + cy.contains('button', 'Dismiss').click(); }); it('should not delete a page used as entity view', () => { diff --git a/cypress/e2e/settings/__image_snapshots__/Translations translations list should be accessible #0.png b/cypress/e2e/settings/__image_snapshots__/Translations translations list should be accessible #0.png index 90aaf5d78bc5741bda3137d5cc3ef89a4ac42e45..fc7cc824502a68cd44282329ac79b632c3a4e73a 100644 GIT binary patch literal 53883 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.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

0cug2F~dK4Oy5gxf!Iq2VESf+;`x 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 { cy.checkA11y(); cy.contains('button', 'Add').click(); }); + cy.contains('tr', 'My filter group 1').contains('button', 'Group').click(); }); it('should check that the table is accessible', () => { @@ -72,137 +73,146 @@ describe('Filters', () => { cy.contains('button', 'Dismiss').click(); }); - it('should edit the group', () => { - cy.contains('td', 'My filter group 1').parent().contains('button', 'Edit').click(); - cy.get('aside').within(() => { - cy.contains('Edit group'); - cy.get('#group-name').clear(); - cy.get('#group-name').type('Ordenes', { delay: 0 }); - cy.contains('button', 'Update').click(); + describe('groups', () => { + it('should edit the group', () => { + cy.contains('tr', 'My filter group 1').contains('button', 'Edit').click(); + cy.get('aside').within(() => { + cy.contains('Edit group'); + cy.get('#group-name').clear(); + cy.get('#group-name').type('Ordenes', { delay: 0 }); + cy.contains('button', 'Update').click(); + }); + cy.contains('td', 'Ordenes'); }); - cy.contains('td', 'Ordenes'); - }); - it('should show the available and currentyl selected templates', () => { - cy.contains('td', 'Ordenes').parent().contains('button', 'Edit').click(); - cy.get('aside').within(() => { - cy.getByTestId('multiselect').within(() => { - cy.get('button').eq(0).click(); - cy.contains('Mecanismo').should('not.exist'); - cy.contains('País').should('not.exist'); - cy.contains('Ordenes de la corte').should('exist'); - cy.contains('Sentencia de la corte').should('exist'); - cy.contains('Ordenes del presidente').should('exist'); - cy.get('button').eq(0).click(); + it('should show the available and currently selected templates', () => { + cy.contains('tr', 'Ordenes').contains('button', 'Edit').click(); + cy.get('aside').within(() => { + cy.getByTestId('multiselect').within(() => { + cy.get('button').eq(0).click(); + cy.contains('Mecanismo').should('not.exist'); + cy.contains('País').should('not.exist'); + cy.contains('Ordenes de la corte').should('exist'); + cy.contains('Sentencia de la corte').should('exist'); + cy.contains('Ordenes del presidente').should('exist'); + cy.get('button').eq(0).click(); + }); }); }); - }); - it('should validate group format', () => { - cy.get('aside').within(() => { - cy.get('#group-name').clear(); - cy.contains('button', 'Update').click(); - cy.contains('This field is required'); - cy.get('#group-name').type('Reportes y causas', { delay: 0 }); - cy.getByTestId('multiselect').within(() => { - cy.get('button').eq(1).click(); - cy.get('button').eq(1).click(); - cy.get('button').eq(1).click(); - }); - cy.contains('button', 'Update').click(); - cy.contains('This field is required'); - cy.getByTestId('multiselect').within(() => { - cy.get('button').eq(0).click(); - cy.contains('Causa').click(); - cy.contains('Report').click(); + it('should validate group format', () => { + cy.get('aside').within(() => { + cy.get('#group-name').clear(); + cy.contains('button', 'Update').click(); + cy.contains('This field is required'); + cy.get('#group-name').type('Reportes y causas', { delay: 0 }); + cy.getByTestId('multiselect').within(() => { + cy.get('button').eq(1).click(); + cy.get('button').eq(1).click(); + cy.get('button').eq(1).click(); + }); + cy.contains('button', 'Update').click(); }); - cy.contains('Update').click(); + cy.contains('tr', 'Reportes y causas').contains('Group').click(); + cy.contains('Empty group. Drop here to add'); }); - cy.contains('td', 'Reportes y causas'); - }); - it('should create a another group', () => { - cy.contains('button', 'Add group').click(); - cy.get('aside').within(() => { - cy.get('#group-name').type('Ordenes', { delay: 0 }); - cy.getByTestId('multiselect').within(() => { - cy.get('button').click(); - cy.contains('Voto Separado').click(); - cy.contains('Ordenes del presidente').click(); - cy.contains('Medida Provisional').click(); - cy.contains('Sentencia de la corte').click(); + it('should create a another group', () => { + cy.contains('button', 'Add group').click(); + cy.get('aside').within(() => { + cy.get('#group-name').type('Ordenes', { delay: 0 }); + cy.getByTestId('multiselect').within(() => { + cy.get('button').click(); + cy.contains('Voto Separado').click(); + cy.contains('Ordenes del presidente').click(); + cy.contains('Medida Provisional').click(); + cy.contains('Sentencia de la corte').click(); + }); + cy.contains('button', 'Add').click(); }); - cy.contains('button', 'Add').click(); }); - cy.contains('button', 'Save').click(); - cy.contains('Filters saved'); - cy.contains('button', 'Dismiss').click(); - }); - it('should verify the library is updated', () => { - cy.contains('a', 'Library').click(); - cy.get('#filtersForm > :nth-child(2) > .search__filter').within(() => { - cy.contains('div.multiselectItem', 'Ordenes').within(() => { - cy.get('span.multiselectItem-action').click(); + it('should create an empty group', () => { + cy.contains('button', 'Add group').click(); + cy.get('aside').within(() => { + cy.get('#group-name').type('Other documents', { delay: 0 }); + cy.contains('button', 'Add').click(); }); - cy.get('[title="Voto Separado"]'); - cy.get('[title="Ordenes del presidente"]'); - cy.get('[title="Medida Provisional"]'); - cy.get('[title="Sentencia de la corte"]'); - cy.contains('div.multiselectItem', 'Reportes y causas'); - cy.get('[title="Mecanismo"]'); - cy.get('[title="País"]'); + cy.contains('tr', 'Other documents').contains('button', 'Group').click(); + cy.contains('Empty group. Drop here to add'); }); - }); - describe('deletion', () => { - it('should delete single filters', () => { - cy.contains('a', 'Settings').click(); - cy.contains('a', 'Filter').click(); - cy.contains('caption', 'Filters').click(); - cy.contains('td', 'Mecanismo') - .parent() - .within(() => { - cy.get('input[type=checkbox]').click(); + it('should edit the group and add an item', () => { + cy.contains('tr', 'Other documents').contains('button', 'Edit').click(); + cy.get('aside').within(() => { + cy.getByTestId('multiselect').within(() => { + cy.get('button').click(); + cy.contains('Juez y/o Comisionado').click(); }); - cy.contains('button', 'Delete').click(); + cy.contains('button', 'Update').click(); + }); + cy.contains('tr', 'Other documents').contains('button', 'Group').click(); + cy.contains('tr', 'Juez y/o Comisionado'); + }); + + it('should save and delete empty groups', () => { cy.contains('button', 'Save').click(); cy.contains('Filters saved'); cy.contains('button', 'Dismiss').click(); + cy.contains('tr', 'Reportes y causas').should('not.exist'); }); + }); - it('should delete group items', () => { - cy.contains('td', 'Reportes y causas').within(() => { - cy.contains('button', 'Group').click(); - }); - cy.contains('td', 'Causa') - .parent() - .within(() => { - cy.get('input[type=checkbox]').click(); + describe('check library', () => { + it('should verify the library is updated', () => { + cy.contains('a', 'Library').click(); + cy.get('#filtersForm > :nth-child(2) > .search__filter').within(() => { + cy.get('[title="Mecanismo"]'); + cy.get('[title="País"]'); + cy.contains('div.multiselectItem', 'Ordenes').within(() => { + cy.get('span.multiselectItem-action').click(); + }); + cy.get('[title="Voto Separado"]'); + cy.get('[title="Ordenes del presidente"]'); + cy.get('[title="Medida Provisional"]'); + cy.get('[title="Sentencia de la corte"]'); + cy.contains('div.multiselectItem', 'Other documents').within(() => { + cy.get('span.multiselectItem-action').click(); }); + cy.get('[title="Juez y/o Comisionado"]'); + cy.contains('div.multiselectItem', 'Reportes y causas').should('not.exist'); + }); + }); + }); + describe('deletion', () => { + it('should delete single filters', () => { + cy.contains('a', 'Settings').click(); + cy.contains('a', 'Filter').click(); + cy.contains('caption', 'Filters').click(); + cy.contains('tr', 'Mecanismo').contains('Select').click(); cy.contains('button', 'Delete').click(); cy.contains('button', 'Save').click(); cy.contains('Filters saved'); cy.contains('button', 'Dismiss').click(); }); - it('should delete a group', () => { - cy.contains('td', 'Ordenes') - .parent() - .within(() => { - cy.get('input[type=checkbox]').click(); - }); + it('should delete group items and an entire group', () => { + cy.contains('tr', 'Ordenes').contains('button', 'Group').click(); + cy.contains('tr', 'Medida Provisional').contains('Select').click(); + cy.contains('tr', 'Other documents').contains('Select').click(); cy.contains('button', 'Delete').click(); cy.contains('button', 'Save').click(); cy.contains('Filters saved'); cy.contains('button', 'Dismiss').click(); }); - it('should check sepecifically deleted items', () => { - cy.contains('td', 'Mecanismo').should('not.exist'); - cy.contains('td', 'Causa').should('not.exist'); - cy.contains('td', 'Ordenes').should('not.exist'); + it('should check the results', () => { + cy.contains('td', 'País'); + cy.contains('tr', 'Ordenes').contains('button', 'Group'); + cy.contains('td', 'Voto Separado'); + cy.contains('td', 'Ordenes del presidente'); + cy.contains('td', 'Sentencia de la corte'); }); }); }); diff --git a/cypress/e2e/settings/menu.cy.ts b/cypress/e2e/settings/menu.cy.ts index 46d8e5be8e..d3834c75ba 100644 --- a/cypress/e2e/settings/menu.cy.ts +++ b/cypress/e2e/settings/menu.cy.ts @@ -11,10 +11,6 @@ describe('Menu configuration', () => { cy.contains('span', 'Menu').click(); }); - it('should have no detectable accessibility violations on load', () => { - cy.checkA11y(); - }); - beforeEach(() => { cy.intercept('GET', 'api/settings/links').as('fetchLinks'); }); @@ -41,10 +37,27 @@ describe('Menu configuration', () => { cy.get('#link-title').type('Link 3', { delay: 0 }); cy.get('#link-url').type('www.exmple.com', { delay: 0 }); cy.getByTestId('menu-form-submit').click(); + }); + it('should alert users of unsaved changes', () => { + cy.contains('a', 'Account').click(); + cy.get('[data-testid="modal"]').within(() => { + cy.contains('You have unsaved changes. Do you want to continue?'); + cy.contains('button', 'Cancel').click(); + }); + }); + + it('should save', () => { cy.getByTestId('menu-save').click(); cy.contains('Dismiss').click(); cy.wait('@fetchLinks'); + cy.getByTestId('menu-save').should('be.disabled'); + }); + + it('should not show the unsaved changes alert', () => { + cy.contains('a', 'Account').click(); + cy.contains('a', 'Menu').click(); + cy.contains('caption', 'Menu'); }); it('tests Add groups', () => { @@ -70,13 +83,24 @@ describe('Menu configuration', () => { cy.get('#link-title').type(' edited', { delay: 0 }); cy.get('#link-group').select('Group 2'); cy.getByTestId('menu-form-submit').click(); + }); + + it('should alert users of unsaved changes after editing', () => { + cy.contains('a', 'Account').click(); + cy.get('[data-testid="modal"]').within(() => { + cy.contains('You have unsaved changes. Do you want to continue?'); + cy.contains('button', 'Cancel').click(); + }); + }); + + it('should save the edited links', () => { cy.getByTestId('menu-save').click(); cy.contains('Dismiss').click(); cy.wait('@fetchLinks'); + cy.getByTestId('menu-save').should('be.disabled'); }); it('tests edit groups', () => { - //open groups cy.get('tbody tr:nth-of-type(3)').contains('button', 'Group').click(); cy.get('tbody tr:nth-of-type(2)').contains('button', 'Group').click(); @@ -84,7 +108,7 @@ describe('Menu configuration', () => { cy.get('#link-group').select('Group 2'); cy.getByTestId('menu-form-submit').click(); - cy.get('tbody tr:nth-of-type(4)').contains('Edit').click(); + cy.get('tbody tr:nth-of-type(5)').contains('Edit').click(); cy.get('#link-group').select('Group 1'); cy.getByTestId('menu-form-submit').click(); @@ -113,4 +137,19 @@ describe('Menu configuration', () => { cy.contains('Dismiss').click(); cy.wait('@fetchLinks'); }); + + it('should have no detectable accessibility violations on load', () => { + cy.checkA11y(); + }); + + it('should verify the changes impacted on the navigation bar', () => { + cy.get('.menuItems > .menuNav-list').within(() => { + cy.contains('Group 1').click(); + cy.get('.dropdown-menu.expanded').should('be.empty'); + cy.contains('Group 2').click(); + cy.get('.dropdown-menu.expanded').within(() => { + cy.contains('Link 1 edited'); + }); + }); + }); }); diff --git a/package.json b/package.json index 789935e160..048548196b 100644 --- a/package.json +++ b/package.json @@ -201,8 +201,6 @@ "react-color": "^2.19.3", "react-datepicker": "7.3.0", "react-device-detect": "^2.2.3", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend-old": "yarn:react-dnd-html5-backend@^15.1.2", "react-dnd-old": "yarn:react-dnd@2.6.0", "react-dom": "^18.3.1", diff --git a/yarn.lock b/yarn.lock index dffb41dc4d..08e3e7f6ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14960,13 +14960,6 @@ react-device-detect@^2.2.3: dependencies: dnd-core "15.1.2" -react-dnd-html5-backend@^16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6" - integrity sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw== - dependencies: - dnd-core "^16.0.1" - "react-dnd-old@yarn:react-dnd@2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.6.0.tgz#7fa25676cf827d58a891293e3c1ab59da002545a" @@ -14986,7 +14979,7 @@ react-dnd-test-backend@15.1.1: dependencies: dnd-core "15.1.1" -react-dnd@*, react-dnd@^16.0.1: +react-dnd@*: version "16.0.1" resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37" integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q== From 0710d1740ab7956cfc7223eec18ac3ba6b065d9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:36:13 -0500 Subject: [PATCH 7/8] Bump @babel/cli from 7.24.8 to 7.25.6 (#7172) * Bump @babel/cli from 7.24.8 to 7.25.6 Bumps [@babel/cli](https://github.com/babel/babel/tree/HEAD/packages/babel-cli) from 7.24.8 to 7.25.6. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.25.6/packages/babel-cli) --- updated-dependencies: - dependency-name: "@babel/cli" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * updates related packages --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mfacar --- package.json | 6 ++-- yarn.lock | 91 +++++++++++++++++++--------------------------------- 2 files changed, 36 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 048548196b..4375f996fd 100644 --- a/package.json +++ b/package.json @@ -251,13 +251,13 @@ }, "devDependencies": { "@4tw/cypress-drag-drop": "^2.2.5", - "@babel/cli": "7.24.8", + "@babel/cli": "7.25.6", "@babel/core": "7.25.2", "@babel/eslint-parser": "7.25.1", "@babel/helper-call-delegate": "^7.12.13", "@babel/helper-get-function-arity": "^7.16.7", "@babel/helper-string-parser": "^7.24.8", - "@babel/parser": "^7.25.4", + "@babel/parser": "^7.25.6", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", @@ -270,7 +270,7 @@ "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", - "@babel/traverse": "^7.25.4", + "@babel/traverse": "^7.25.6", "@cfaester/enzyme-adapter-react-18": "^0.8.0", "@chromatic-com/storybook": "^1.6.1", "@cypress/react18": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 08e3e7f6ab..841a8b7f62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,10 +623,10 @@ "@smithy/types" "^3.3.0" tslib "^2.6.2" -"@babel/cli@7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.24.8.tgz#79eaa55a69c77cafbea3e87537fd1df5a5a2edf8" - integrity sha512-isdp+G6DpRyKc+3Gqxy2rjzgF7Zj9K0mzLNnxz+E/fgeag8qT3vVulX4gY9dGO1q0y+0lUv6V3a+uhUzMzrwXg== +"@babel/cli@7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.25.6.tgz#bc35561adc78ade43ac9c09a690768493ab9ed95" + integrity sha512-Z+Doemr4VtvSD2SNHTrkiFZ1LX+JI6tyRXAAOb4N9khIuPyoEPmTPJarPm8ljJV1D6bnMQjyHMWTT9NeKbQuXA== dependencies: "@jridgewell/trace-mapping" "^0.3.25" commander "^6.2.0" @@ -637,7 +637,7 @@ slash "^2.0.0" optionalDependencies: "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.4.0" + chokidar "^3.6.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": version "7.24.7" @@ -682,12 +682,12 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.24.4", "@babel/generator@^7.25.0", "@babel/generator@^7.25.4", "@babel/generator@^7.7.2": - version "7.25.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.5.tgz#b31cf05b3fe8c32d206b6dad03bb0aacbde73450" - integrity sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w== +"@babel/generator@^7.24.4", "@babel/generator@^7.25.0", "@babel/generator@^7.25.4", "@babel/generator@^7.25.6", "@babel/generator@^7.7.2": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" + integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== dependencies: - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" @@ -895,12 +895,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.4", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" - integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.4", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" + integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== dependencies: - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": version "7.25.3" @@ -1758,23 +1758,23 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" -"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.1", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e" - integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg== +"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.1", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4", "@babel/traverse@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" + integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.4" - "@babel/parser" "^7.25.4" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" "@babel/template" "^7.25.0" - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.16.7", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.24.0", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f" - integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ== +"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.16.7", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.24.0", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.25.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" + integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== dependencies: "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" @@ -6994,10 +6994,10 @@ child-process-promise@^2.2.1: node-version "^1.0.0" promise-polyfill "^6.0.1" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -16426,16 +16426,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16522,14 +16513,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17978,7 +17962,7 @@ world-countries@5.0.0: resolved "https://registry.yarnpkg.com/world-countries/-/world-countries-5.0.0.tgz#6f75ebcce3d5224d84e9117eaf0d75a7726b6501" integrity sha512-wAfOT9Y5i/xnxNOdKJKXdOCw9Q3yQLahBUeuRol+s+o20F6h2a4tLEbJ1lBCYwEQ30Sf9Meqeipk1gib3YwF5w== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -17996,15 +17980,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 4c71f4b8775b6ac85e3af39ded2a2eef24201d89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:11:12 -0500 Subject: [PATCH 8/8] Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#7186) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_unit_tests.yml b/.github/workflows/ci_unit_tests.yml index 9ba402f631..b4fb9b71f3 100644 --- a/.github/workflows/ci_unit_tests.yml +++ b/.github/workflows/ci_unit_tests.yml @@ -114,7 +114,7 @@ jobs: needs: [api_unit_tests, app_unit_tests] steps: - name: Download tests results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 - name: Download codeclimate binay run: curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - run: chmod +x ./cc-test-reporter